Skip to content

Instantly share code, notes, and snippets.

@robatwilliams
Last active January 10, 2018 12:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robatwilliams/f627be425d2c6ff276c0c85fd3f30159 to your computer and use it in GitHub Desktop.
Save robatwilliams/f627be425d2c6ff276c0c85fd3f30159 to your computer and use it in GitHub Desktop.
Asset allocation glide path chart

Reproduction of a real asset allocation glide path chart using D3 and D3FC.

It shows the investment fund moving towards safer assets as the target date nears.

Uses ES6 features, so you need a modern browser.

#!/bin/bash
# Converts Vanguard US XML data from https://investor.vanguard.com/ts/glidepath/trfGlideChartRetail.xml
xml-js data.xml --spaces 4 --compact --native-type --out data.json
{
"_declaration": {
"_attributes": {
"version": "1.0",
"encoding": "UTF-8"
}
},
"xml": {
"_attributes": {
"version": "1.0"
},
"chartdata": {
"_attributes": {
"name": "Asset allocation by age",
"chtType": "continuous",
"xAxisTitle": "Years until retirement",
"yAxisTitle": "Portfolio Allocation",
"yAxisUnit": "%"
},
"legend": {
"label": [
{
"_attributes": {
"name": "U.S. Stocks",
"color": "0x96151D"
}
},
{
"_attributes": {
"name": "Int'l Stocks",
"color": "0xF0AB00"
}
},
{
"_attributes": {
"name": "U.S. Nominal Bonds",
"color": "0x005393"
}
},
{
"_attributes": {
"name": "Int'l Nominal Bonds",
"color": "0x1896D3"
}
},
{
"_attributes": {
"name": "Short-Term TIPS",
"color": "0x13AAE2"
}
}
]
},
"content": {
"content0": {
"_text": "3,000+ stocks representing the U.S. market"
},
"content1": {
"_text": "2,000+ stocks representing European, Pacific, and emerging markets"
},
"content2": {
"_text": "U.S. Treasuries designed to keep pace with inflation"
},
"content3": {
"_text": "Up to 3,000 bonds representing the U.S. market"
},
"content4": {
"_text": "High-quality, short-term money market securities"
}
},
"linedata": {
"data": [
{
"_attributes": {
"interval": "50"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"_attributes": {
"interval": "45"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"_attributes": {
"interval": "40"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"_attributes": {
"interval": "35"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"_attributes": {
"interval": "30"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"_attributes": {
"interval": "25"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 54
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 36
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 53.55
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 35.7
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 7.525
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3.225
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 52.65
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 35.1
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 8.575
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 3.675
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 51.75
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 34.5
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 9.625
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 4.125
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 50.85
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 33.9
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 10.675
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 4.575
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"_attributes": {
"interval": "20"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 49.95
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 33.3
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 11.725
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 5.025
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 49.05
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 32.7
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 12.775
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 5.475
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 48.15
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 32.1
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 13.825
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 5.925
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 47.25
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 31.5
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 14.875
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 6.375
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 46.35
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 30.9
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 15.925
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 6.825
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"_attributes": {
"interval": "15"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 45.45
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 30.3
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 16.975
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 7.275
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 44.55
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 29.7
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 18.025
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 7.725
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 43.65
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 29.1
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 19.075
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 8.175
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 42.75
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 28.5
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 20.125
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 8.625
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 41.85
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 27.9
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 21.175
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 9.075
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"_attributes": {
"interval": "10"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 40.95
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 27.3
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 22.225
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 9.525
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 40.05
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 26.7
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 23.275
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 9.975
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 39.15
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 26.1
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 24.325
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 10.425
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 38.25
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 25.5
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 25.375
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 10.875
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 37.35
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 24.9
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 26.425
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 11.325
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"_attributes": {
"interval": "5"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 36.45
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 24.3
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 27.475
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 11.775
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 35.4
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 23.6
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 28.14
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 12.06
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 0.8
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 34.2
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 22.8
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 28.42
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 12.18
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 2.4
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 33
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 22
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 28.7
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 12.3
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 4
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 31.8
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 21.2
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 28.98
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 12.42
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 5.6
}
]
},
{
"_attributes": {
"interval": "Retirement"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 30.6
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 20.4
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 29.26
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 12.54
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 7.2
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 29.143
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 19.429
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 29.96
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 12.84
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 8.629
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 27.429
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 18.286
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 31.08
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 13.32
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 9.886
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 25.714
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 17.143
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 32.2
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 13.8
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 11.143
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 24
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 16
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 33.32
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 14.28
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 12.4
}
]
},
{
"_attributes": {
"interval": "+5"
},
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 22.286
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 14.857
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 34.44
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 14.76
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 13.657
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 20.571
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 13.714
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 35.56
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 15.24
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 14.914
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 18.857
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 12.571
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 36.68
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 15.72
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 16.171
}
]
},
{
"item": [
{
"_attributes": {
"name": "U.S. Stocks"
},
"_text": 18
},
{
"_attributes": {
"name": "International Stocks"
},
"_text": 12
},
{
"_attributes": {
"name": "U.S. Nominal Bonds"
},
"_text": 37.24
},
{
"_attributes": {
"name": "International Nominal Bonds"
},
"_text": 15.96
},
{
"_attributes": {
"name": "Short-Term TIPS"
},
"_text": 16.8
}
]
}
]
}
}
}
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>glide-path</title>
<link rel="stylesheet" href="style.css" />
<script src="https://unpkg.com/d3@4.12.2/build/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.25.4/d3-legend.js"></script>
<script src="https://unpkg.com/d3fc@13.1.1/build/d3fc.js"></script>
</head>
<body>
<div id="chart-container" class="container fill">
<div data-id="chart" class="chart"></div>
<svg data-id="legend" class="legend"></svg>
</div>
<script src="index.js"></script>
</body>
</html>
(function () {
const fontSize = 12;
const shapeMargin = 10;
d3.json('data.json', (error, data) => {
if (error) throw error;
const lineData = niceifyLineDataStructure(data.xml.chartdata.linedata.data);
const legendData = data.xml.chartdata.legend.label
.map(asset => ({ ...asset._attributes }));
// repeat last point to indicate continuation at fixed allocation
const expandedLine = [...lineData, lineData[lineData.length - 1]];
// adjust; point n is "the year to n"
const startYear = -lineData[0].interval - 1;
const endYear = startYear + lineData.length;
const fundTargetYearAbs = 2050; // absolute. relative, it's year 0 in the data
render(expandedLine, startYear, endYear, fundTargetYearAbs, legendData);
});
function render(data, startYear, endYear, targetYearAbs, legendData) {
const stack = d3.stack()
.keys(data[0].assets.map(asset => asset.name))
.value((d, key) => d.assets.find(asset => asset.name === key).allocation);
const stackedSeries = stack(data);
const color = d3.scaleOrdinal(legendData.map(asset => asset.color.replace('0x', '#')))
.domain(stackedSeries.map(series => series.key));
const legend = createLegend(color, legendData);
const xScale = d3.scaleLinear()
.domain([startYear, endYear]);
const chart = fc.chartSvgCartesian(
xScale,
d3.scaleLinear())
.yDomain([0, 1]);
chart
.yOrient('left')
.yTicks(5)
.yTickFormat(percentFormatter)
.yLabel('Portfolio allocation');
chart
.xTickFormat(yearsToGoFormatter)
.xLabel('Years until retirement')
.xDecorate(currentYearAxisMarker(xScale, targetYearAbs));
chart
.yTickSize([0]).xTickSize([0])
.yTickPadding(5).xTickPadding(5);
chart.chartLabel(targetYearAbs + ' Target date retirement fund - glide path');
// consumes stacked data, so effective y value accessors are configured on the d3.stack configuration
const areaSeries = fc.seriesSvgArea()
.crossValue((d, i) => startYear + i)
.mainValue(d => d[1])
.baseValue(d => d[0]);
const gridlines = fc.annotationSvgGridline()
.yTicks(0);
const multiSeries = fc.seriesSvgMulti()
.series([...stackedSeries.map(() => areaSeries), gridlines])
.mapping((d, i) => d[i] || d[d.length - 1]) // data is stacked by index
.decorate(stackedAreasColourer(color, stackedSeries));
chart.plotArea(multiSeries);
const containerEl = d3.select('#chart-container');
const chartEl = containerEl.select('[data-id=chart]');
const legendEl = containerEl.select('[data-id=legend]');
legendEl.call(legend)
.selectAll('.label').call(repositionLabelsAdjacent);
legendEl.select('.legendCells')
.attr('transform', `translate(${4 * fontSize})`); // align with plot area, which has 4em left margin
chartEl.datum(stackedSeries)
.call(chart);
}
function stackedAreasColourer(scale, series) {
return function colorStackedAreas(selection) {
selection.each((d, i, nodes) => {
d3.select(nodes[i])
.select('path.area')
.attr('fill', () => scale(series[i].key)); // use func so it only evaluates for area series
});
};
}
function currentYearAxisMarker(xScale, targetYearAbs) {
const currentYearAbs = new Date().getFullYear();
const currentYearRel = currentYearAbs - targetYearAbs;
const markerSymbol = d3.symbol()
.type(d3.symbolTriangle);
let markerEl;
return function addCurrentYearAxisMarker(selection) {
if (!selection.enter().empty()) {
const axis = d3.select(selection.node().parentNode);
markerEl = axis.append('path')
.attr('d', markerSymbol)
.attr('transform', `translate(${xScale(currentYearRel)},10)`)
.classed('current-year-marker', true);
markerEl.append('title')
.text(`${-currentYearRel} years to go until ${targetYearAbs} (currently ${currentYearAbs})`);
}
if (markerEl) {
// reposition on resize
markerEl.attr('transform', `translate(${xScale(currentYearRel)},10)`);
}
// remove ticks that would conflict with the marker
selection.enter().filter(d => d === currentYearRel).remove();
};
}
function createLegend(scale, legendData) {
const shapePadding = 100;
return d3.legendColor()
.scale(scale)
.orient('horizontal')
.shapePadding(shapePadding) // make space for long labels
.labelWrap(shapePadding - shapeMargin) // leave gap before next item's shape
.labelAlign('start')
.labels(({ i }) => legendData[i].name); // use shortened names
}
function repositionLabelsAdjacent(selection) {
selection.each((d, i, nodes) => {
const shape = nodes[i].previousSibling;
const dx = shape.getBBox().width + shapeMargin;
const dy = fontSize * -1.75;
const transform = nodes[i].transform.baseVal[0];
transform.setTranslate(transform.matrix.e + dx, transform.matrix.f + dy);
});
}
function percentFormatter(value, i, group) {
const formatted = d3.format('.0%')(value);
const last = i === group.length - 1;
return last ? formatted : formatted.replace('%', ' ');
}
function yearsToGoFormatter(years) {
switch (Math.sign(years)) {
case -1: return -years;
case 0: return 'Retirement';
case 1: return '+' + years;
}
}
function niceifyLineDataStructure(data) {
return data.map(point => ({
...point._attributes,
assets: point.item.map(item => ({
...item._attributes,
allocation: item._text / 100
}))
}));
}
})();
.container {
display: flex;
flex-direction: column;
font: 12px sans-serif;
}
.container.fill {
height: 500px;
}
.container.small {
width: 700px;
height: 250px;
}
.chart {
flex: 1;
}
.legend {
height: 30px;
margin-top: 15px;
}
.chart-label {
font-size: 1.2em;
font-weight: bold;
}
.y-axis .domain,
.x-axis .domain {
display: none;
}
.y-axis-label {
white-space: nowrap;
}
.y-axis .tick text {
white-space: pre;
}
.x-axis .current-year-marker {
fill: black;
}
.area,
.gridline-y {
stroke: white;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment