Skip to content

Instantly share code, notes, and snippets.

@jsundram
Last active December 31, 2025 20:35
Show Gist options
  • Select an option

  • Save jsundram/a187ff2619b90eaf68d449462b1f9795 to your computer and use it in GitHub Desktop.

Select an option

Save jsundram/a187ff2619b90eaf68d449462b1f9795 to your computer and use it in GitHub Desktop.
Periodic Table of Boccherini String Quartets
# macOS system files
.DS_Store
# Local workflow files
.gist_id
sync-gist.sh

Periodic Table of Boccherini String Quartets

An interactive visualization of Luigi Boccherini's complete string quartet output (1761-1804), displayed as a periodic table-inspired grid.

View it (via gisthack, see below for details):

Updating the Gist

Quick Start (First Time Setup)

# 1. Configure git to use GitHub CLI credentials (do this once)
gh auth setup-git

# 2. Save your gist ID (do this once)
gh gist list | head -1 | awk '{print $1}' > .gist_id

# 3. Create a sync script (do this once)
cat > sync-gist.sh << 'EOF'
#!/bin/bash

# Usage: ./sync-gist.sh "commit message" file1 file2 ...
# Or: ./sync-gist.sh file1 file2 ... (uses default message)

# If first arg looks like a commit message (has spaces or quotes), use it
if [[ "$1" == *" "* ]] || [[ "$1" == \"*\" ]]; then
    MESSAGE="$1"
    shift
    FILES="$@"
else
    MESSAGE="Update gist"
    FILES="${@:-index.html}"
fi

# Add, commit, and push with message
git add $FILES
git commit -m "$MESSAGE"
git push
EOF
chmod +x sync-gist.sh

Daily Workflow

# 1. Make your changes to index.html, opera.json, or README.md

# 2. Check what changed
git diff                    # See all changes
git diff index.html         # See specific file changes

# 3. Update the gist with a commit message
./sync-gist.sh "Fix title alignment" index.html

# Or update multiple files
./sync-gist.sh "Update visualization and docs" index.html README.md

# Or use default message "Update gist"
./sync-gist.sh index.html

How it works

The script uses standard git commands to update the gist:

  1. git add - stages your files
  2. git commit -m "message" - commits with your message
  3. git push - pushes to the gist

Your commit messages appear in the gist history instead of just "revised".

Manual method (if you prefer)

# Standard git workflow
git add index.html
git commit -m "Your commit message"
git push

# Check status
git status
git log --oneline

Viewing Online

Get the raw file URL and use githack.com to serve it:

  1. Get the raw URL from your gist:

    https://gist.githubusercontent.com/USERNAME/GIST_ID/raw/index.html
    
  2. Replace gist.githubusercontent.com with gist.githack.com:

    https://gist.githack.com/USERNAME/GIST_ID/raw/index.html
    

Example for this gist:

https://gist.githack.com/jsundram/a187ff2619b90eaf68d449462b1f9795/raw/index.html

Why githack.com?

  • Properly serves files with correct MIME types
  • Loads external scripts (like D3.js) without CORS issues
  • No rate limiting for development use
  • Updates automatically when you update your gist

Note: htmlpreview.github.io doesn't work because it can't load external scripts like D3.js.

Data Overview (opera.json)

Structure

The data is organized as an array of opus groups, where each opus contains:

{
  "opus": 2,                    // Opus number
  "year": 1761,                 // Year of composition
  "dedication": "...",          // Optional: dedicatee
  "imslp": "...",              // Optional: IMSLP link for the opus
  "quartets": [...]            // Array of quartets in this opus
}

Each quartet contains:

{
  "number": 1,                 // Quartet number within opus
  "gerard": 159,               // Gerard catalog number
  "key": "C",                  // Key (e.g., "C", "E-flat")
  "major": false,              // true = major, false = minor
  "nickname": "...",           // Optional: nickname
  "imslp": "...",             // Optional: IMSLP link
  "mvmts": [...],             // Array of movement names
  "category": "opera grande"   // "opera grande" or "opera piccola" variants
}

Data Usage

Displayed in visualization:

  • Opus number (row header)
  • Year and Boccherini's age (row header, top)
  • Category: Opera Grande/Piccola (row header, bottom)
  • Dedication (row header, when present)
  • Gerard catalog number (card header)
  • Quartet number within opus (card header)
  • Key signature (card center, with ♭ symbol)
  • Major/minor mode (card center)
  • Movement count with color coding (card bottom)
  • Nickname (card center, when present)

Used in interactions:

  • Individual movement names (shown in hover tooltip)
  • IMSLP links (quartet and opus level, opened on click)

Notable transformations:

  • "-flat" in key names is replaced with the Unicode flat symbol (♭)
  • Boccherini's age calculated from birth year (1743)
  • Category determines row background gradient color

Unused/Metadata

All data fields are currently utilized either in the display, tooltips, or interactions. The data is comprehensive and fully integrated.

Technical Overview (index.html)

Architecture

Single-file design: All HTML, CSS, and JavaScript in one file for easy gist hosting and sharing.

Technology stack:

  • D3.js v7 for data loading and DOM manipulation
  • Pure CSS for styling (no CSS frameworks)
  • Vanilla JavaScript (no additional frameworks)

Design Decisions

1. Periodic Table Metaphor

  • Square cards (140×140px) arranged in rows by opus
  • Each opus group forms a "period" (row)
  • Inspired by chemical periodic table organization

2. Visual Alignment Strategy The design emphasizes vertical alignment across three levels:

Row Header          ↔  Quartet Cards
─────────────────────────────────────
Year (age)          ↔  Mode bar (G# / quartet #)
Opus number         ↔  Key signature
Category badge      ↔  Movement count

This creates strong visual relationships between semantically related information.

3. Color Encoding System

Mode bar (top of each card):

  • Blue (#2196F3): Major keys
  • Pink (#E91E63): Minor keys

Movement count (diverging purple-green palette):

  • 1 movement: Deep purple (#9C27B0) - rare/incomplete
  • 2 movements: Light purple (#CE93D8) - opera piccola
  • 3 movements: Blue-gray (#B0BEC5) - standard
  • 4 movements: Light green (#81C784) - substantial
  • 5 movements: Deep green (#2E7D32) - rare/complete

Category badges use the same palette:

  • Opera Piccola: Light purple (matches 2-movement works)
  • Opera Grande: Light green (matches 4-movement works)

Row background gradients:

  • Subtle gradient (12% opacity) extends from row header toward cards
  • Uses category color to create visual flow across the row

4. Layout System

Flexbox throughout:

  • Rows: display: flex with flex-wrap for cards
  • Opus labels: justify-content: space-between for vertical distribution
  • Cards: flex-direction: column for stacking elements

Height constraints:

  • Opus label and cards both fixed at 140px for alignment
  • flex-grow: 1 on middle sections (opus number, key signature) for vertical centering
  • Movement count uses margin-top: auto to anchor to bottom

5. Typography Hierarchy

Dramatic size contrast in row headers:

  • Opus number: 2.8em, weight 900 (ultra bold)
  • Year: 0.8em, weight 600
  • Age: 0.65em (parenthetical)
  • Category badge: 0.65em
  • Dedication: 0.6em, italic

Code Intricacies

1. Commented sections for easy tweaking The opus label CSS and JavaScript are heavily commented with clear section markers (e.g., === OPUS LABEL SECTION ===) to facilitate experimentation with layout and sizing.

2. Dynamic class application Category background gradients are applied dynamically:

const categoryClass = opus.quartets[0].category.includes('grande')
  ? 'grande-bg' : 'piccola-bg';

3. Unicode transformation Flat symbols are rendered using Unicode replacement:

const keyDisplay = quartet.key.replace('-flat', '♭');

4. Nested container pattern Cards use multiple nested containers for precise alignment:

  • quartet-cardmode-bar + card-content
  • card-contentkey-section + nickname + movements-count

This allows independent control of each vertical section.

5. Age calculation Boccherini's age is calculated inline from a constant:

const BOCCHERINI_BIRTH_YEAR = 1743;
const age = opus.year - BOCCHERINI_BIRTH_YEAR;

Browser Compatibility

Requires modern browser support for:

  • CSS Flexbox
  • Unicode symbols (♭)
  • ES6 JavaScript (const, arrow functions, template literals)
  • D3.js v7

Tested in Chrome, Firefox, Safari, and Edge (2023+).


Created: December 2025 Data source: Luigi Boccherini quartet catalog (G.159-249)

#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "playwright>=1.40.0",
# ]
# ///
"""
Generate PDF of Boccherini String Quartets visualization
Usage:
uv run generate-pdf.py
First-time setup (install Playwright browsers):
uvx playwright install chromium
Requirements:
- uv installed (https://docs.astral.sh/uv/)
- Local server running at http://localhost:8000
"""
import sys
from playwright.sync_api import sync_playwright
def generate_pdf():
"""Generate the PDF with exact settings"""
print("📄 Generating Boccherini Quartets PDF...")
print(" Loading http://localhost:8000/index.html")
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
# Navigate to the page
page.goto('http://localhost:8000/index.html', wait_until='networkidle')
# Generate PDF with exact settings
page.pdf(
path='boccherini-quartets.pdf',
format='Letter',
print_background=True, # Enable background graphics
margin={
'top': '0.25in', # Top margin for spacing
'bottom': '0in', # Minimum margins
'left': '0in',
'right': '0in'
},
prefer_css_page_size=True # Use CSS @page size settings
)
browser.close()
print("✓ PDF saved to: boccherini-quartets.pdf")
if __name__ == "__main__":
try:
generate_pdf()
except Exception as e:
if "Executable doesn't exist" in str(e) or "browserType.launch" in str(e):
print("\n⚠️ Playwright browsers not installed.")
print("\nPlease run this command first:")
print(" uvx playwright install chromium")
sys.exit(1)
else:
raise
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Periodic Table of Boccherini String Quartets</title>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<style>
:root {
/* === ALIGNMENT SYSTEM === */
--card-height: 140px;
--top-section-height: 20px;
--nickname-height: 15px; /* Fixed height for nickname section */
--bottom-section-height: 20px; /* Fixed height for badge/movement count */
/* Calculated middle section height - same for both sides */
--middle-section-height: calc(
var(--card-height)
- var(--top-section-height)
- var(--nickname-height)
- var(--bottom-section-height)
);
/* = 140px - 20px - 15px - 20px = 85px */
/* Font sizes for aligned elements */
--top-font-size: 0.8em;
--middle-font-size: 2.8em;
--bottom-font-size: 0.7em;
/* Header widths */
--header-width: 900px;
/* === DEBUG MODE === */
--debug-mode: 0; /* Set to 1 to show bounding boxes, 0 to hide */
}
/* Debug bounding boxes - controlled by --debug-mode */
.opus-label .year-age {
outline: calc(var(--debug-mode) * 1px) solid rgba(255, 0, 0, 0.3);
}
.opus-label .opus-number {
outline: calc(var(--debug-mode) * 1px) solid rgba(0, 255, 0, 0.3);
}
.opus-label .bottom-section {
outline: calc(var(--debug-mode) * 1px) solid rgba(0, 0, 255, 0.3);
}
.mode-bar {
outline: calc(var(--debug-mode) * 1px) solid rgba(255, 0, 0, 0.3);
}
.key-section {
outline: calc(var(--debug-mode) * 1px) solid rgba(0, 255, 0, 0.3);
}
.movements-count {
outline: calc(var(--debug-mode) * 1px) solid rgba(0, 0, 255, 0.3);
}
.nickname {
outline: calc(var(--debug-mode) * 1px) solid rgba(255, 165, 0, 0.3);
}
.opus-label .dedication {
outline: calc(var(--debug-mode) * 1px) solid rgba(128, 0, 128, 0.3);
}
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
background-color: #f5f5f5;
margin: 20px;
padding: 0;
}
.header-group {
margin-left: 95px; /* Shift right by opus-label width */
}
h1 {
max-width: var(--header-width);
margin: 0 auto 5px auto; /* Center horizontally, small gap below */
text-align: center;
color: #333;
font-size: 2em;
}
.subtitle {
max-width: var(--header-width); /* Same width as title for alignment */
margin: 0 auto 25px auto; /* Center horizontally, larger gap below */
text-align: right; /* Right-align text within container */
color: #666;
font-size: 0.9em;
font-style: italic;
}
.subtitle a {
color: #2196F3;
text-decoration: none;
}
.subtitle a:hover {
text-decoration: underline;
}
.container {
max-width: 1400px;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
}
.opus-row {
display: flex;
align-items: flex-start;
margin-bottom: 15px;
}
/* Combined rows - no special CSS needed, just flex layout */
.opus-row.combined-pair {
/* Flexbox will lay out multiple label+card groups horizontally */
}
/* === OPUS LABEL SECTION (easy to tweak) === */
.opus-label {
/* Container sizing - MATCHES CARD HEIGHT */
width: 95px;
height: var(--card-height);
padding: 2px 6px 0 10px; /* 2px top, 6px right, 0 bottom, 10px left */
/* Layout - vertical alignment structure */
display: flex;
flex-direction: column;
justify-content: flex-start; /* Stack from top, no auto-spacing */
text-align: right;
position: relative;
}
/* Background gradient based on category - applied via JS */
.opus-label.grande-bg {
background: linear-gradient(to right,
transparent 0%,
rgba(129, 199, 132, 0.12) 100%); /* Light green fade */
}
.opus-label.piccola-bg {
background: linear-gradient(to right,
transparent 0%,
rgba(206, 147, 216, 0.12) 100%); /* Light purple fade */
}
/* TOP SECTION: Year and Age - ALIGNS WITH MODE BAR */
.opus-label .year-age {
display: flex;
justify-content: space-between; /* Year left, age right - periodic table aesthetic */
align-items: center;
height: var(--top-section-height);
flex-shrink: 0;
line-height: 1; /* Match mode-bar line-height */
}
.opus-label .year {
font-size: var(--top-font-size);
font-weight: 700; /* Slightly bolder for clarity */
color: #444; /* Slightly darker */
line-height: 1; /* Exact alignment */
}
.opus-label .age {
font-size: calc(var(--top-font-size) * 0.85); /* Slightly larger */
font-weight: 400; /* Regular weight */
color: #777; /* Slightly darker */
line-height: 1; /* Exact alignment */
}
/* MIDDLE SECTION: Opus number - ALIGNS WITH KEY/MODE */
.opus-label .opus-number {
font-size: var(--middle-font-size);
font-weight: 900;
color: #222;
line-height: 1;
letter-spacing: -0.03em;
height: var(--middle-section-height); /* Fixed height to match key-section */
margin-bottom: var(--nickname-height); /* Spacer to match nickname section */
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-end;
}
/* BOTTOM SECTION: Dedication and category badge */
.opus-label .bottom-section {
display: flex;
flex-direction: column;
align-items: flex-end; /* Right align */
justify-content: center; /* Vertically center - matches movements-count */
flex-shrink: 0;
height: var(--bottom-section-height); /* Fixed height to match movements-count */
position: relative; /* For dedication positioning */
}
/* Dedication - absolutely positioned above opus number to preserve alignment */
.opus-label .dedication {
position: absolute; /* Remove from layout flow */
top: 22px; /* Position below year/age */
left: 0;
right: 0;
font-size: 0.6em; /* Size: smallest */
font-style: italic; /* Style: italic */
color: #999; /* Color: lighter gray */
text-align: center; /* Center align */
line-height: 1.2; /* Tighter line height for wrapping */
z-index: 1; /* Above background */
}
/* Category badge - aligns with movement count */
.opus-label .category-badge {
font-size: var(--bottom-font-size); /* Aligns with movement count */
width: 100%; /* Full width like movement count */
height: 100%; /* Full height like movement count */
display: flex; /* Use flexbox for centering */
align-items: center; /* Vertically center text */
justify-content: center; /* Horizontally center text */
font-weight: 500; /* Match movement count weight */
line-height: 1; /* Match movement count */
}
/* === END OPUS LABEL SECTION === */
.quartets-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-left: 10px; /* Gap between label and cards */
}
/* Spacer between opus groups in combined rows */
.opus-spacer {
width: 177px; /* Calculated for pixel-perfect vertical alignment */
height: var(--card-height);
flex-shrink: 0;
margin: 0 10px; /* Gap on both sides */
}
.quartet-card {
width: 140px;
height: var(--card-height); /* Matches opus-label height */
background: white;
border: 2px solid #ddd;
border-radius: 4px;
padding: 0;
box-shadow: 2px 2px 5px rgba(0,0,0,0.1);
cursor: pointer;
transition: all 0.2s;
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
}
.quartet-card:hover {
transform: translateY(-3px);
box-shadow: 3px 3px 10px rgba(0,0,0,0.2);
border-color: #888;
}
.mode-bar {
height: var(--top-section-height); /* Aligns with year-age section */
width: 100%;
flex-shrink: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 8px;
box-sizing: border-box;
}
.mode-bar.major {
background: transparent;
}
.mode-bar.minor {
background: #E91E63;
}
.card-content {
padding: 0;
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
/* No position: relative - nickname positions relative to .quartet-card instead */
}
/* Text colors for major keys (on transparent background) */
.mode-bar.major .quartet-number {
font-size: var(--top-font-size); /* Aligns with year */
color: #888; /* Lighter gray */
font-weight: 600; /* Semi-bold */
line-height: 1; /* Exact alignment */
}
.mode-bar.major .gerard-number {
font-size: var(--top-font-size); /* Aligns with year */
font-weight: 700; /* Bold for emphasis */
color: #444; /* Darker for readability */
line-height: 1; /* Exact alignment */
}
/* Text colors for minor keys (on colored background) */
.mode-bar.minor .quartet-number {
font-size: var(--top-font-size); /* Aligns with year */
color: rgba(255, 255, 255, 0.85); /* Slightly less opaque */
font-weight: 600; /* Semi-bold */
line-height: 1; /* Exact alignment */
}
.mode-bar.minor .gerard-number {
font-size: var(--top-font-size); /* Aligns with year */
font-weight: 700; /* Bold for emphasis */
color: white;
line-height: 1; /* Exact alignment */
}
/* Key section - aligns with opus number */
.key-section {
height: var(--middle-section-height); /* Fixed height to match opus-number */
margin-bottom: var(--nickname-height); /* Space for nickname below (matches opus-number margin-bottom) */
flex-shrink: 0;
display: flex;
flex-direction: column;
justify-content: center; /* Vertically center */
align-items: center; /* Horizontally center */
padding: 0 8px; /* Horizontal padding only */
}
.key-signature {
text-align: center;
font-size: calc(var(--middle-font-size) * 0.64); /* Proportional to opus number */
font-weight: bold;
color: #222;
line-height: 1;
margin: 0; /* Remove margin for proper centering */
}
.key-mode {
text-align: center;
font-size: 0.75em;
color: #666;
margin: 0; /* Remove margin for proper centering */
line-height: 1;
margin-top: 2px; /* Small space above, not below key-signature */
}
.category-badge.grande {
background-color: #81C784;
color: #333;
}
.category-badge.piccola {
background-color: #CE93D8;
color: #333;
}
/* Links container for quartet cards - holds IMSLP and QR links */
.card-links {
display: flex;
justify-content: space-between; /* IMSLP left, QR right */
align-items: center;
padding: 2px 4px;
font-size: 0.65em;
font-weight: 400;
}
.imslp-link,
.qr-link {
color: #2196F3;
text-decoration: none;
}
.imslp-link:hover,
.qr-link:hover {
text-decoration: underline;
color: #1976D2;
}
/* IMSLP link in opus label - positioned absolutely to not affect layout */
.opus-label .imslp-link {
position: absolute;
bottom: 100%; /* Position above bottom-section */
left: 0; /* Left align to match quartet cells */
margin-bottom: 1px; /* Minimal space below link */
padding: 2px 4px; /* Match quartet cell link padding */
font-size: 0.65em; /* Match quartet cell link size */
font-weight: 400; /* Lighter weight */
}
.movements-count {
text-align: center;
font-size: var(--bottom-font-size); /* Aligns with category badge */
height: var(--bottom-section-height); /* Fixed height to match bottom-section */
flex-shrink: 0;
display: flex;
align-items: center; /* Vertically center text */
justify-content: center; /* Horizontally center text */
margin: 0;
padding: 0; /* No padding - height is explicit */
border-radius: 0; /* Square edges like top bar */
font-weight: 500;
line-height: 1; /* Match category badge */
}
/* Diverging color scheme: Purple (short) -> Gray (standard) -> Green (long) */
.movements-count.mvmt-1 {
background-color: #9C27B0;
color: white;
}
.movements-count.mvmt-2 {
background-color: #CE93D8;
color: #333;
}
.movements-count.mvmt-3 {
background-color: #B0BEC5;
color: #333;
}
.movements-count.mvmt-4 {
background-color: #81C784;
color: #333;
}
.movements-count.mvmt-5 {
background-color: #2E7D32;
color: white;
}
.nickname {
position: absolute; /* Remove from layout flow to preserve alignment */
top: 22px; /* Match dedication positioning */
left: 0;
right: 0;
max-height: 13px; /* Constrain to allocated space (22px to 35px) */
overflow: hidden; /* Clip overflow to prevent overlapping key section */
font-size: 0.55em; /* Slightly smaller for tighter fit in limited space */
font-style: italic; /* Same as dedication */
color: #999; /* Same as dedication */
text-align: center; /* Center align */
line-height: 1; /* Tight line-height to maximize space */
z-index: 2; /* Above mode bar */
pointer-events: none; /* Don't block clicks on mode bar */
}
.tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 0.85em;
pointer-events: none;
z-index: 1000;
max-width: 300px;
line-height: 1.4;
}
.dedication {
font-size: 0.7em;
color: #666;
font-style: italic;
margin-left: 5px;
}
/* === PRINT STYLES === */
/*
* PRINTING INSTRUCTIONS:
* For best results when printing or saving as PDF:
* 1. Open print dialog (Cmd+P / Ctrl+P)
* 2. Enable "Background graphics"
* 3. Set margins to "Custom" with:
* - Top: 0.25 inches
* - Bottom: 0 inches (minimum)
* - Left: 0 inches (minimum)
* - Right: 0 inches (minimum)
* Note: Browser print dialogs override CSS @page margins,
* so custom margins must be set manually.
*/
@media print {
@page {
margin: 0; /* Reset all margins to 0 first */
margin-top: 0.25in; /* Only top margin */
size: letter portrait;
}
body {
font-size: 9pt;
background: white !important;
zoom: 0.73; /* Scale to 73% to fit on page */
}
/* Page break controls */
h1, .subtitle {
page-break-after: avoid;
}
.opus-row {
page-break-inside: avoid;
break-inside: avoid;
}
/* Clean aesthetics for print */
.opus-label {
background: none !important;
}
.opus-label.grande-bg,
.opus-label.piccola-bg {
background: none !important;
}
.quartet-card {
background: none !important;
box-shadow: none !important;
border: 1px solid #999;
}
.mode-bar.major {
background: transparent !important;
}
/* Hide interactive elements */
.tooltip {
display: none !important;
}
/* Ensure links are visible */
.imslp-link,
.qr-link {
color: #2196F3;
}
}
/* === MOBILE RESPONSIVE === */
@media (max-width: 768px) {
body {
margin: 10px;
}
.header-group {
margin-left: 0;
text-align: center;
}
h1 {
font-size: 1.5em;
margin-bottom: 10px;
}
.subtitle {
font-size: 0.8em;
text-align: center;
margin-bottom: 15px;
}
.container {
max-width: 100%;
align-items: flex-start;
}
.opus-row {
flex-wrap: wrap;
margin-bottom: 10px;
width: 100%;
}
/* On mobile, opus label takes full width, then cards wrap below */
.opus-label {
width: 100%;
text-align: left;
height: auto;
min-height: var(--card-height);
margin-bottom: 5px;
}
.quartets-container {
margin-left: 0;
width: 100%;
justify-content: center;
}
/* Hide spacers on mobile - not needed when rows wrap */
.opus-spacer {
display: none;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header-group">
<h1>Luigi Boccherini (1743–1805) – 91 String Quartets</h1>
<p class="subtitle">see also <a href="https://quartetroulette.com/Boccherini/" target="_blank">Quartet Roulette</a></p>
</div>
<div id="visualization"></div>
</div>
<script>
// Load the opera.json data
d3.json('opera.json').then(data => {
const container = d3.select('#visualization');
// Create tooltip
const tooltip = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
// === HELPER FUNCTIONS ===
// Render opus label (left side of row)
function renderOpusLabel(parent, opus) {
// Determine category for background gradient
const categoryClass = opus.quartets.length > 0 &&
opus.quartets[0].category.includes('grande') ? 'grande-bg' : 'piccola-bg';
const opusLabel = parent.append('div')
.attr('class', `opus-label ${categoryClass}`);
// === TOP: Year + Age (aligns with mode bar) ===
const BOCCHERINI_BIRTH_YEAR = 1743;
const age = opus.year - BOCCHERINI_BIRTH_YEAR;
const yearAge = opusLabel.append('div')
.attr('class', 'year-age');
yearAge.append('span')
.attr('class', 'year')
.text(opus.year);
yearAge.append('span')
.attr('class', 'age')
.text(`(${age})`);
// === Dedication (if present) - appears above opus number ===
if (opus.dedication) {
// Update dedications for display
let dedicationDisplay = opus.dedication;
if (opus.dedication === 'Monsieur le Baron du Beine de Malchamps') {
dedicationDisplay = 'Baron de Malchamps';
} else if (opus.dedication === 'Alli Signori Diletanti di Madrid') {
dedicationDisplay = 'Diletanti di Madrid';
} else if (opus.dedication === 'Infante Luis of Spain') {
dedicationDisplay = 'Infante Luigi di Spagna';
}
opusLabel.append('div')
.attr('class', 'dedication')
.text(dedicationDisplay);
}
// === MIDDLE: Opus number (NO "Op." prefix) ===
opusLabel.append('div')
.attr('class', 'opus-number')
.text(opus.opus);
// === BOTTOM: Bottom section (IMSLP link + category badge) ===
const bottomSection = opusLabel.append('div')
.attr('class', 'bottom-section');
// IMSLP link (if present at opus level)
if (opus.imslp) {
bottomSection.append('a')
.attr('class', 'imslp-link')
.attr('href', opus.imslp)
.attr('target', '_blank')
.attr('rel', 'noopener noreferrer')
.text('imslp')
.on('click', function(event) {
event.stopPropagation();
});
}
// Category badge below dedication/link
if (opus.quartets.length > 0) {
const category = opus.quartets[0].category;
const catClass = category.includes('grande') ? 'grande' : 'piccola';
const categoryText = category.includes('grande') ? 'Grande' : 'Piccola';
bottomSection.append('div')
.attr('class', `category-badge ${catClass}`)
.text(categoryText);
}
return opusLabel;
}
// Render quartet card
function renderQuartetCard(parent, opus, quartet, tooltip) {
let activeTooltip = false;
const showTooltip = function(event) {
d3.select(this).style('border-color', '#333');
const movements = quartet.mvmts.map((m, i) => `${i + 1}. ${m}`).join('<br>');
const keyFull = `${quartet.key} ${quartet.major ? 'major' : 'minor'}`;
let tooltipText = `<strong>Opus ${opus.opus} #${quartet.number || '—'} \
in ${keyFull}, G. ${quartet.gerard}</strong><br>`;
if (quartet.nickname) {
tooltipText += `<em>"${quartet.nickname}"</em><br>`;
}
tooltipText += `${quartet.category}<br><br>`;
tooltipText += `<strong>Movements:</strong><br>${movements}`;
const x = event.pageX || (event.touches && event.touches[0].pageX) || 0;
const y = event.pageY || (event.touches && event.touches[0].pageY) || 0;
tooltip.html(tooltipText)
.style('left', (x + 10) + 'px')
.style('top', (y - 10) + 'px')
.style('opacity', 1);
activeTooltip = true;
};
const hideTooltip = function() {
d3.select(this).style('border-color', '#ddd');
tooltip.style('opacity', 0);
activeTooltip = false;
};
const card = parent.append('div')
.attr('class', 'quartet-card')
.on('mouseover', showTooltip)
.on('mouseout', hideTooltip)
.on('click', function(event) {
// Toggle tooltip on mobile tap
if ('ontouchstart' in window) {
event.preventDefault();
if (activeTooltip) {
hideTooltip.call(this);
} else {
showTooltip.call(this, event);
}
}
});
// Add colored top bar for major/minor with numbers
const modeBar = card.append('div')
.attr('class', `mode-bar ${quartet.major ? 'major' : 'minor'}`);
// Gerard catalog number in the mode bar (left)
modeBar.append('div')
.attr('class', 'gerard-number')
.text(quartet.gerard);
// Quartet number (within opus) in the mode bar (right)
modeBar.append('div')
.attr('class', 'quartet-number')
.text(quartet.number ? `#${quartet.number}` : '');
// Create content container
const content = card.append('div')
.attr('class', 'card-content');
// Nickname if exists - appears above key section
if (quartet.nickname) {
content.append('div')
.attr('class', 'nickname')
.text(`"${quartet.nickname}"`);
}
// Key section (aligns with opus number in row header)
const keySection = content.append('div')
.attr('class', 'key-section');
// Key signature (replace -flat with ♭ symbol)
const keyDisplay = quartet.key.replace('-flat', '♭');
keySection.append('div')
.attr('class', 'key-signature')
.text(keyDisplay);
// Major/minor mode
keySection.append('div')
.attr('class', 'key-mode')
.text(quartet.major ? 'major' : 'minor');
// Links container (IMSLP and QR) - appended to card, positioned above movement bar
const linksContainer = card.append('div')
.attr('class', 'card-links');
// IMSLP link (left-aligned)
const imslpLink = quartet.imslp || opus.imslp;
if (imslpLink) {
linksContainer.append('a')
.attr('class', 'imslp-link')
.attr('href', imslpLink)
.attr('target', '_blank')
.attr('rel', 'noopener noreferrer')
.text('imslp')
.on('click', function(event) {
event.stopPropagation(); // Prevent card click
});
} else {
// Empty span to maintain spacing when no IMSLP link
linksContainer.append('span');
}
// QR link (right-aligned) - always present
linksContainer.append('a')
.attr('class', 'qr-link')
.attr('href', `https://quartetroulette.com/boccherini-g${quartet.gerard}/`)
.attr('target', '_blank')
.attr('rel', 'noopener noreferrer')
.text('QR')
.on('click', function(event) {
event.stopPropagation(); // Prevent card click
});
// Movement count (appended to card, not content, so it's at the bottom)
const mvmtCount = quartet.mvmts.length;
card.append('div')
.attr('class', `movements-count mvmt-${mvmtCount}`)
.text(`${mvmtCount} movement${mvmtCount === 1 ? '' : 's'}`);
return card;
}
// === AUTO-FLOW CONFIGURATION ===
const excludeFromCombining = new Set([64]); // Historical significance (Boccherini's final opus)
const maxQuartetsPerRow = 4; // Limit combined rows to max 4 quartets (e.g., 1+2 or 2+2)
const maxQuartetsToConsiderForCombining = 2; // Only combine opuses with ≤2 quartets
let currentRowOpuses = [];
let currentRowQuartetCount = 0;
// === RENDERING FUNCTIONS ===
// Render a single opus on its own row
function renderSingleRow(container, opus) {
const row = container.append('div')
.attr('class', 'opus-row');
// Render opus label
renderOpusLabel(row, opus);
// Quartets container
const quartetContainer = row.append('div')
.attr('class', 'quartets-container');
// Render quartet cards
opus.quartets.forEach(quartet => {
renderQuartetCard(quartetContainer, opus, quartet, tooltip);
});
}
// Render multiple opuses on a combined row with spacers
function renderCombinedRow(container, ...opuses) {
const row = container.append('div')
.attr('class', 'opus-row combined-pair');
opuses.forEach((opus, index) => {
// Render opus label
renderOpusLabel(row, opus);
// Quartets container for this opus
const quartetContainer = row.append('div')
.attr('class', 'quartets-container');
// Render quartet cards
opus.quartets.forEach(quartet => {
renderQuartetCard(quartetContainer, opus, quartet, tooltip);
});
// Add spacer between opus groups (but not after the last one)
if (index < opuses.length - 1) {
row.append('div')
.attr('class', 'opus-spacer');
}
});
}
// === MAIN RENDERING LOOP WITH AUTO-FLOW ===
data.forEach((opus, index) => {
const quartetCount = opus.quartets.length;
if (excludeFromCombining.has(opus.opus) || quartetCount > maxQuartetsToConsiderForCombining) {
// Render accumulated row if any, then render this opus alone
if (currentRowOpuses.length > 0) {
renderCombinedRow(container, ...currentRowOpuses);
currentRowOpuses = [];
currentRowQuartetCount = 0;
}
renderSingleRow(container, opus);
} else {
// Try to add to current row
if (currentRowQuartetCount + quartetCount <= maxQuartetsPerRow) {
currentRowOpuses.push(opus);
currentRowQuartetCount += quartetCount;
} else {
// Current row full, render it and start new row
if (currentRowOpuses.length > 0) {
renderCombinedRow(container, ...currentRowOpuses);
}
currentRowOpuses = [opus];
currentRowQuartetCount = quartetCount;
}
}
});
// Render any remaining accumulated row
if (currentRowOpuses.length > 0) {
renderCombinedRow(container, ...currentRowOpuses);
}
}).catch(error => {
console.error('Error loading opera.json:', error);
d3.select('#visualization')
.append('p')
.style('color', 'red')
.text('Error loading data. Please ensure opera.json is in the same directory as this HTML file.');
});
</script>
</body>
</html>
[
{
"opus": 2,
"year": 1761,
"imslp": "https://imslp.org/wiki/6_String_Quartets,_Op.2_(Boccherini,_Luigi)",
"quartets": [
{
"number": 1,
"gerard": 159,
"key": "C",
"major": false,
"imslp": "https://imslp.org/wiki/String_Quartet_in_C_minor%2C_G.159_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro comodo",
"Largo",
"Allegro"
],
"category": "opera grande"
},
{
"number": 2,
"gerard": 160,
"key": "B-flat",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_B-flat_major%2C_G.160_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro non tanto",
"Largo",
"Fuga con spirito"
],
"category": "opera grande"
},
{
"number": 3,
"gerard": 161,
"key": "D",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_D_major%2C_G.161_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro moderato",
"Largo",
"Minuetto"
],
"category": "opera grande"
},
{
"number": 4,
"gerard": 162,
"key": "E-flat",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_E-flat_major%2C_G.162_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro spiritoso",
"Adagio",
"Minuetto"
],
"category": "opera grande"
},
{
"number": 5,
"gerard": 163,
"key": "E",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_E_major%2C_G.163_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro moderato",
"Adagio",
"Allegro assai"
],
"category": "opera grande"
},
{
"number": 6,
"gerard": 164,
"key": "C",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_C_major%2C_G.164_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro con spirito",
"Largo",
"Minuetto"
],
"category": "opera grande"
}
]
},
{
"opus": 8,
"year": 1768,
"dedication": "Infante Luis of Spain",
"quartets": [
{
"number": 1,
"gerard": 165,
"key": "D",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_D_major%2C_G.165_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro assai",
"Adagio",
"Allegro Rondeau"
],
"category": "opera grande"
},
{
"number": 2,
"gerard": 166,
"key": "C",
"major": false,
"imslp": "https://imslp.org/wiki/String_Quartet_in_C_minor%2C_G.166_(Boccherini%2C_Luigi)",
"mvmts": [
"Moderato",
"Largo",
"Allegro"
],
"category": "opera grande"
},
{
"number": 3,
"gerard": 167,
"key": "E-flat",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_E-flat_major%2C_G.167_(Boccherini%2C_Luigi)",
"mvmts": [
"Largo",
"Allegro",
"Tempo di Minuetto"
],
"category": "opera grande"
},
{
"number": 4,
"gerard": 168,
"key": "G",
"major": false,
"imslp": "https://imslp.org/wiki/String_Quartet_in_G_minor%2C_G.168_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro",
"Grave",
"Allegro"
],
"category": "opera grande"
},
{
"number": 5,
"gerard": 169,
"key": "F",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_F_major%2C_G.169_(Boccherini%2C_Luigi)",
"mvmts": [
"Andantino",
"Allegro",
"Tempo di Minuetto"
],
"category": "opera grande"
},
{
"number": 6,
"gerard": 170,
"key": "A",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_A_major%2C_G.170_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro brillante",
"Amoroso",
"Allegro"
],
"category": "opera grande"
}
]
},
{
"opus": 9,
"year": 1770,
"dedication": "Alli Signori Diletanti di Madrid",
"quartets": [
{
"number": 1,
"gerard": 171,
"key": "C",
"major": false,
"imslp": "https://imslp.org/wiki/String_Quartet_in_C_minor%2C_G.171_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro",
"Larghetto",
"Minuetto",
"Presto"
],
"category": "opera grande"
},
{
"number": 2,
"gerard": 172,
"key": "D",
"major": false,
"imslp": "https://imslp.org/wiki/String_Quartet_in_D_minor%2C_G.172_(Boccherini%2C_Luigi)",
"mvmts": [
"Grave - Allegro",
"Larghetto",
"Allegretto con moto"
],
"category": "opera grande"
},
{
"number": 3,
"gerard": 173,
"key": "F",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_F_major%2C_G.173_(Boccherini%2C_Luigi)",
"mvmts": [
"Largo - Allegro",
"Largo cantabile",
"Minuetto"
],
"category": "opera grande"
},
{
"number": 4,
"gerard": 174,
"key": "E-flat",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_E-flat_major%2C_G.174_(Boccherini%2C_Luigi)",
"mvmts": [
"Adagio",
"Allegro",
"Minuetto"
],
"category": "opera grande"
},
{
"number": 5,
"gerard": 175,
"key": "D",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_D_major%2C_G.175_(Boccherini%2C_Luigi)",
"mvmts": [
"Andante con moto",
"Allegro assai",
"Rondo Allegro"
],
"category": "opera grande"
},
{
"number": 6,
"gerard": 176,
"key": "E",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_E_major%2C_G.176_(Boccherini%2C_Luigi)",
"mvmts": [
"Andante grazioso",
"Allegretto",
"Minuetto",
"Allegro assai"
],
"category": "opera grande"
}
]
},
{
"opus": 15,
"year": 1772,
"quartets": [
{
"number": 1,
"gerard": 177,
"key": "D",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_D_major%2C_G.177_(Boccherini%2C_Luigi)",
"mvmts": [
"Presto",
"Allegro rondeau"
],
"category": "opera piccola"
},
{
"number": 2,
"gerard": 178,
"key": "F",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_F_major%2C_G.178_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegretto",
"Allegro Minuetto"
],
"category": "opera piccola"
},
{
"number": 3,
"gerard": 179,
"key": "E",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_E_major%2C_G.179_(Boccherini%2C_Luigi)",
"mvmts": [
"Andantino",
"Prestissimo"
],
"category": "opera piccola"
},
{
"number": 4,
"gerard": 180,
"key": "F",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_F_major%2C_G.180_(Boccherini%2C_Luigi)",
"mvmts": [
"Prestissimo",
"Minuetto"
],
"category": "opera piccola"
},
{
"number": 5,
"gerard": 181,
"key": "E-flat",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_E-flat_major%2C_G.181_(Boccherini%2C_Luigi)",
"mvmts": [
"Adagio con sordina",
"Minuetto"
],
"category": "opera piccola"
},
{
"number": 6,
"gerard": 182,
"key": "C",
"major": false,
"imslp": "https://imslp.org/wiki/String_Quartet_in_C_minor%2C_G.182_(Boccherini%2C_Luigi)",
"mvmts": [
"Larghetto",
"Minuetto. Allegro moderato"
],
"category": "opera piccola"
}
]
},
{
"opus": 22,
"year": 1775,
"imslp": "https://imslp.org/wiki/6_String_Quartets,_G.183-188_(Op.22)_(Boccherini,_Luigi)",
"quartets": [
{
"number": 1,
"gerard": 183,
"key": "C",
"major": true,
"mvmts": [
"Allegro molto",
"Tempo di Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 2,
"gerard": 184,
"key": "D",
"major": true,
"mvmts": [
"Moderato",
"Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 3,
"gerard": 185,
"key": "E-flat",
"major": true,
"mvmts": [
"Adagio",
"Rondeau Allegro"
],
"category": "quartettino ... opera piccola"
},
{
"number": 4,
"gerard": 186,
"key": "B-flat",
"major": true,
"mvmts": [
"Allegretto moderato",
"Allegro vivace"
],
"category": "quartettino ... opera piccola"
},
{
"number": 5,
"gerard": 187,
"key": "A",
"major": false,
"mvmts": [
"Allegro con molto",
"Minuetto Amoroso"
],
"category": "quartettino ... opera piccola"
},
{
"number": 6,
"gerard": 188,
"key": "C",
"major": true,
"mvmts": [
"Andantino",
"Non troppo presto"
],
"category": "quartettino ... opera piccola"
}
]
},
{
"opus": 24,
"year": 1777,
"imslp": "https://imslp.org/wiki/6_String_Quartets,_G.189-194_(Op.24)_(Boccherini,_Luigi)",
"quartets": [
{
"number": 1,
"gerard": 189,
"key": "D",
"major": true,
"mvmts": [
"Moderato",
"Grave",
"Allegro assai"
],
"category": "opera grande"
},
{
"number": 2,
"gerard": 190,
"key": "A",
"major": true,
"mvmts": [
"Larghetto",
"Allegro spiritoso",
"Minuetto Amoroso"
],
"category": "opera grande"
},
{
"number": 3,
"gerard": 191,
"key": "E-flat",
"major": true,
"mvmts": [
"Allegro moderato",
"Adagio non tanto",
"Minuetto"
],
"category": "opera grande"
},
{
"number": 4,
"gerard": 192,
"key": "C",
"major": true,
"mvmts": [
"Moderato",
"Larghetto",
"Minuetto"
],
"category": "opera grande"
},
{
"number": 5,
"gerard": 193,
"key": "C",
"major": false,
"mvmts": [
"Allegro moderato",
"Larghetto",
"Allegro molto"
],
"category": "opera grande"
},
{
"number": 6,
"gerard": 194,
"key": "G",
"major": false,
"mvmts": [
"Allegro vivo assai",
"Adagio",
"Minuetto"
],
"category": "opera grande"
}
]
},
{
"opus": 26,
"year": 1778,
"imslp": "https://imslp.org/wiki/6_String_Quartets,_G.195-200_(Op.26)_(Boccherini,_Luigi)",
"dedication": "Monsieur le Baron du Beine de Malchamps",
"quartets": [
{
"number": 1,
"gerard": 195,
"key": "B-flat",
"major": true,
"mvmts": [
"Allegro moderato",
"Minuetto con moto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 2,
"gerard": 196,
"key": "G",
"major": false,
"mvmts": [
"Larghetto",
"Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 3,
"gerard": 197,
"key": "E-flat",
"major": true,
"mvmts": [
"Allegro vivace",
"Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 4,
"gerard": 198,
"key": "A",
"major": true,
"mvmts": [
"Larghetto",
"Minuetto con moto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 5,
"gerard": 199,
"key": "F",
"major": true,
"mvmts": [
"Allegretto",
"Minuetto Allegro"
],
"category": "quartettino ... opera piccola"
},
{
"number": 6,
"gerard": 200,
"key": "F",
"major": false,
"mvmts": [
"Andante appassionato ma con lento",
"Minuetto"
],
"category": "quartettino ... opera piccola"
}
]
},
{
"opus": 32,
"year": 1780,
"imslp": "https://imslp.org/wiki/6_String_Quartets,_G.201-206_(Op.32)_(Boccherini,_Luigi)",
"quartets": [
{
"number": 1,
"gerard": 201,
"key": "E-flat",
"major": true,
"mvmts": [
"Allegretto lentarello e affettuoso",
"Minuetto",
"Grave",
"Allegro vivace assai"
],
"category": "opera grande"
},
{
"number": 2,
"gerard": 202,
"key": "E",
"major": false,
"mvmts": [
"Largo sostenuto",
"Minuetto",
"Larghetto",
"Minuetto (same!)",
"Rondeau comodo assai"
],
"category": "opera grande"
},
{
"number": 3,
"gerard": 203,
"key": "D",
"major": true,
"mvmts": [
"Allegro vivo",
"Adagio",
"Allegro vivo ma non presto"
],
"category": "opera grande"
},
{
"number": 4,
"gerard": 204,
"key": "C",
"major": true,
"mvmts": [
"Allegro bizarro",
"Larghetto",
"Allegro con brio"
],
"category": "opera grande"
},
{
"number": 5,
"gerard": 205,
"key": "G",
"major": false,
"mvmts": [
"Allegro comodo",
"Andantino",
"Minuetto con moto",
"Allegro giusto"
],
"category": "opera grande"
},
{
"number": 6,
"gerard": 206,
"key": "A",
"major": true,
"mvmts": [
"Allegro",
"Andante lentarello",
"Minuetto con moto",
"Presto assai"
],
"category": "opera grande"
}
]
},
{
"opus": 33,
"year": 1781,
"quartets": [
{
"number": 1,
"gerard": 207,
"key": "E",
"major": true,
"mvmts": [
"Allegro spiritoso",
"Rondeau. Allegretto ma con moto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 2,
"gerard": 208,
"key": "C",
"major": true,
"mvmts": [
"Allegretto",
"Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 3,
"gerard": 209,
"key": "G",
"major": true,
"mvmts": [
"Andante con moto",
"Presto assai"
],
"category": "quartettino ... opera piccola"
},
{
"number": 4,
"gerard": 210,
"key": "B-flat",
"major": true,
"mvmts": [
"Andante lentarello",
"Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 5,
"gerard": 211,
"key": "E",
"major": false,
"mvmts": [
"Allegro brillante",
"Allegro vivo assai"
],
"category": "quartettino ... opera piccola"
},
{
"number": 6,
"gerard": 212,
"key": "E-flat",
"major": true,
"mvmts": [
"Adagio",
"Minuetto. Affetuoso"
],
"category": "quartettino ... opera piccola"
}
]
},
{
"opus": 39,
"year": 1787,
"quartets": [
{
"number": null,
"gerard": 213,
"key": "A",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_A_major%2C_G.213_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro moderato",
"Minuetto",
"Grave",
"Allegro giusto"
],
"category": "opera grande"
}
]
},
{
"opus": 41,
"year": 1788,
"imslp": "https://imslp.org/wiki/2_String_Quartets,_G.214-215_(Op.41)_(Boccherini,_Luigi)",
"quartets": [
{
"number": 1,
"gerard": 214,
"key": "C",
"major": false,
"mvmts": [
"Prestissimo",
"Tempo di Minuetto",
"Andante Flebile",
"Prestissimo (repeat of second half of 1)"
],
"category": "opera grande"
},
{
"number": 2,
"gerard": 215,
"key": "C",
"major": true,
"mvmts": [
"Allegretto moderato assai",
"Minuetto. Allegro",
"Larghetto affettuoso",
"Rondeau. Allegro moderato"
],
"category": "opera grande"
}
]
},
{
"opus": 42,
"year": 1789,
"quartets": [
{
"number": 1,
"gerard": 216,
"key": "A",
"major": true,
"mvmts": [
"Allegretto moderato",
"Minuetto. Allegro"
],
"category": "quartettino ... opera piccola"
},
{
"number": 2,
"gerard": 217,
"key": "C",
"major": true,
"mvmts": [
"Andante",
"Minuetto"
],
"category": "quartettino ... opera piccola"
}
]
},
{
"opus": 43,
"year": 1790,
"quartets": [
{
"number": 1,
"gerard": 218,
"key": "A",
"major": true,
"mvmts": [
"Allegretto moderato",
"Tempo di Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 2,
"gerard": 219,
"key": "A",
"major": true,
"mvmts": [
"Allegretto con moto",
"Minuetto"
],
"category": "quartettino ... opera piccola"
}
]
},
{
"opus": 44,
"year": 1792,
"quartets": [
{
"number": 1,
"gerard": 220,
"key": "B-flat",
"major": true,
"mvmts": [
"Maestoso assai",
"Tempo di Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 2,
"gerard": 221,
"key": "E",
"major": false,
"mvmts": [
"Andante larghetto",
"Minuetto amoroso",
"Andante allegretto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 3,
"gerard": 222,
"key": "F",
"major": true,
"mvmts": [
"Lento assai",
"Allegretto con moto",
"Tempo di Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 4,
"gerard": 223,
"key": "G",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_G_major%2C_G.223_'La_Tiranna'_(Boccherini%2C_Luigi)",
"nickname": "la tiranna",
"mvmts": [
"Presto",
"Tempo di Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 5,
"gerard": 224,
"key": "D",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_D_major%2C_G.224_(Boccherini%2C_Luigi)",
"mvmts": [
"Andantino lento",
"Allegro non tanto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 6,
"gerard": 225,
"key": "E-flat",
"major": true,
"mvmts": [
"Andantino",
"Minuetto on moto"
],
"category": "quartettino ... opera piccola"
}
]
},
{
"opus": 48,
"year": 1794,
"quartets": [
{
"number": 1,
"gerard": 226,
"key": "F",
"major": true,
"mvmts": [
"Andante moderato",
"Moderato con moto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 2,
"gerard": 227,
"key": "A",
"major": true,
"mvmts": [
"Andante lento",
"Tempo di Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 3,
"gerard": 228,
"key": "B",
"major": false,
"mvmts": [
"Allegretto moderato",
"Minuetto con moto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 4,
"gerard": 229,
"key": "E-flat",
"major": true,
"mvmts": [
"Andantino lento",
"Minuetto con un poco di moto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 5,
"gerard": 230,
"key": "G",
"major": true,
"mvmts": [
"Larghetto",
"Minuetto con un poco di moto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 6,
"gerard": 231,
"key": "C",
"major": true,
"mvmts": [
"Allegro vivace",
"Tempo di Minuetto affettuoso"
],
"category": "quartettino ... opera piccola"
}
]
},
{
"opus": 52,
"year": 1795,
"imslp": "https://imslp.org/wiki/4_String_Quartets,_G.232-235_(Op.52)_(Boccherini,_Luigi)",
"quartets": [
{
"number": 1,
"gerard": 232,
"key": "C",
"major": true,
"mvmts": [
"Allegro con moto",
"Minuetto",
"Adagio",
"Finale. Allegro giusto"
],
"category": "opera grande"
},
{
"number": 2,
"gerard": 233,
"key": "D",
"major": true,
"mvmts": [
"Allegro vivace assai",
"Andantino Patetico",
"Minuetto",
"Rondeau. Allegretto"
],
"category": "opera grande"
},
{
"number": 3,
"gerard": 234,
"key": "G",
"major": true,
"mvmts": [
"Allegretto con moto",
"Minuetto",
"Adagio",
"Rondeau. Allegro giusto"
],
"category": "opera grande"
},
{
"number": 4,
"gerard": 235,
"key": "F",
"major": false,
"mvmts": [
"Allegretto appassionato",
"Minuetto con modo",
"Adagio non tanto",
"Finale. Allegro assai"
],
"category": "opera grande"
}
]
},
{
"opus": 53,
"year": 1796,
"quartets": [
{
"number": 1,
"gerard": 236,
"key": "E-flat",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_E-flat_major%2C_G.236_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro (che appena si senta)",
"Tempo di Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 2,
"gerard": 237,
"key": "D",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_D_major%2C_G.237_(Boccherini%2C_Luigi)",
"mvmts": [
"Andantino Pausato",
"Minuetto. Allegro"
],
"category": "quartettino ... opera piccola"
},
{
"number": 3,
"gerard": 238,
"key": "C",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_C_major%2C_G.238_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro vivace",
"Minuetto. Allegro"
],
"category": "quartettino ... opera piccola"
},
{
"number": 4,
"gerard": 239,
"key": "A",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_A_major%2C_G.239_(Boccherini%2C_Luigi)",
"mvmts": [
"Allegro vivace",
"Rondeau. Allegro"
],
"category": "quartettino ... opera piccola"
},
{
"number": 5,
"gerard": 240,
"key": "C",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_C_major%2C_G.240_(Boccherini%2C_Luigi)",
"mvmts": [
"Andantino lento",
"Tempo di Minuetto"
],
"category": "quartettino ... opera piccola"
},
{
"number": 6,
"gerard": 241,
"key": "E-flat",
"major": true,
"imslp": "https://imslp.org/wiki/String_Quartet_in_E-flat_major%2C_G.241_(Boccherini%2C_Luigi)",
"mvmts": [
"Andantino amoroso",
"Minuetto. Allegro"
],
"category": "quartettino ... opera piccola"
}
]
},
{
"opus": 58,
"year": 1799,
"imslp": "https://imslp.org/wiki/6_String_Quartets,_G.242-247_(Op.58)_(Boccherini,_Luigi)",
"quartets": [
{
"number": 1,
"gerard": 242,
"key": "C",
"major": true,
"mvmts": [
"Allegro",
"Larghetto",
"Allegro vivo assai"
],
"category": "opera grande"
},
{
"number": 2,
"gerard": 243,
"key": "E-flat",
"major": true,
"mvmts": [
"Allegretto lento",
"Minuetto. Allegro",
"Larghetto Malincolico",
"Finale. Allegro vivo assai"
],
"category": "opera grande"
},
{
"number": 3,
"gerard": 244,
"key": "B-flat",
"major": true,
"mvmts": [
"Larghetto",
"Allegro vivo assai",
"Rondeau. Allegro giusto"
],
"category": "opera grande"
},
{
"number": 4,
"gerard": 245,
"key": "B",
"major": false,
"mvmts": [
"Allegro molto",
"Andantino lento",
"Rondeau. Allegro"
],
"category": "opera grande"
},
{
"number": 5,
"gerard": 246,
"key": "D",
"major": true,
"nickname": "Le Cornamuse",
"mvmts": [
"Andante sostenuto",
"Allegretto Gajo",
"Andante sostenuto como primo",
"Presto"
],
"category": "opera grande"
},
{
"number": 6,
"gerard": 247,
"key": "E-flat",
"major": true,
"mvmts": [
"Allegretto moderato",
"Larghetto",
"Allegro giusto e sostenuto"
],
"category": "opera grande"
}
]
},
{
"opus": 64,
"year": 1804,
"dedication": "Luciano Bonaparte",
"quartets": [
{
"number": 1,
"gerard": 248,
"key": "F",
"major": true,
"mvmts": [
"Allegro molto",
"Adagio non tanto",
"Allegro vivo ma non presto"
],
"category": "opera grande"
},
{
"number": 2,
"gerard": 249,
"key": "D",
"major": true,
"mvmts": [
"Allegro con brio"
],
"category": "opera grande"
}
]
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment