In my real job as a portfolio manager, asset allocation is very important, so we monitor on a routine basis. For the weekly update, I used reactable
with a little custom d3
JavaScript and SVG to visualize the current allocation compared to investment policy ranges and targets. Below is a quick example with entirely made up (but I think realistic) numbers.
Code
library(htmltools)
library(d3r)
library(reactable)
# example portfolio allocation data frame
# in general this will likely come from another source
alloc <- data.frame(
asset = c("Fixed Income","Equity","Real Estate", "Cash"),
val = c(1400000000, 750000000, 500000000, 100000000),
pct = rep(NA,4), # will calculate later
target_pct = c(0.50,0.25,0.20,0.05),
target_val = rep(NA,4), # will calculate later
diff = rep(NA,4), # will calculate later
min = c(0.40,0.20,0.15,0.02),
max = c(0.60,0.30,0.25,0.10)
)
# calculate actual percent
alloc$pct <- alloc$val / sum(alloc$val)
# calculate target value
alloc$target_val <- sum(alloc$val) * alloc$target_pct
# calculate actual to target difference
alloc$diff <- alloc$val - alloc$target_val
rt <- reactable(
alloc,
pagination = FALSE,
sortable = FALSE,
compact = TRUE,
style = list(fontFamily = "sans-serif"),
# turn off borders here
borderless = TRUE,
rowStyle = list(
fontSize = "1.25rem",
alignItems = "center",
# add back row borders here
borderBottom = "1px solid lightgray"
),
defaultColDef = colDef(
headerStyle = "align-self: flex-end; font-weight: normal; text-align:center;"
),
columns = list(
asset = colDef(
header = JS("function(cellInfo){return `<div></div>`}"),
headerStyle = "text-transform:uppercase; align-self:flex-end; font-weight:normal;",
html = TRUE,
cell = JS("
function(cellInfo) {
return `
<div>
${cellInfo.value}
</div>
`
}
")
),
val = colDef(
header = JS("function(cellInfo){return `<div>$</div>`}"),
cell = JS("
function(cellInfo) {
if(cellInfo.index === 0) {
return `<div>${'$ ' + d3.format(',.0f')(cellInfo.value)}</div>`
} else {
return `<div>${d3.format(',.0f')(cellInfo.value)}</div>`
}
}
"),
html = TRUE
),
pct = colDef(
header = JS("function(cellInfo){return `<div>%</div>`}"),
cell = JS("
function(cellInfo) {
if(cellInfo.row.asset === '') return null
const height = 50
const width = 400
const pady = 12
const padx = 20
return `
<svg height = ${height} width = ${width}>
<rect
x='${scalex(cellInfo.row.min)}'
y='${pady}'
width='${scalex(cellInfo.row.max)-scalex(cellInfo.row.min)}'
height='${height-(2*pady)}'
fill='#ccc'
/>
<line
x1='${scalex(cellInfo.row.target_pct)}'
y1='${pady}'
x2='${scalex(cellInfo.row.target_pct)}'
y2='${height-pady}'
stroke='white'
stroke-width='2'
/>
<line
x1='${scalex(cellInfo.row.pct)}'
y1='${pady}'
x2='${scalex(cellInfo.row.pct)}'
y2='${height-pady}'
stroke='#4fb1ff'
stroke-width='2'
/>
<text
x='${scalex(cellInfo.row.min)-2}'
y='${height/2 + 4}'
text-anchor='end'
style='font-size:11px;'
>
${d3.format('.0%')(cellInfo.row.min)}
</text>
<text
x='${scalex(cellInfo.row.max)+2}'
y='${height/2 + 4}'
text-anchor='start'
style='font-size:11px;'
>
${d3.format('.0%')(cellInfo.row.max)}
</text>
<text
x='${scalex(cellInfo.row.target_pct)}'
y='${height - pady/2 + 4}'
text-anchor='middle'
style='font-size:11px;'
>
${d3.format('.0%')(cellInfo.row.target_pct)}
</text>
<text
x='${scalex(cellInfo.row.pct)}'
y='${pady - 4}'
text-anchor='middle'
fill='#4fb1ff'
style='font-size:11px;'
>
${d3.format('.0%')(cellInfo.row.pct)}
</text>
<text
x='${scalex(1) + 20}'
y='${height/2 + 20}'
text-anchor='end'
fill='${cellInfo.row.pct > cellInfo.row.target_pct ? 'black' : 'red'}'
style='font-size:20px;'
>
${
cellInfo.row.pct > cellInfo.row.target_pct ?
'+' + d3.format('.1%')(cellInfo.row.pct - cellInfo.row.target_pct) :
d3.format('.1%')(cellInfo.row.pct - cellInfo.row.target_pct)
}
</text>
</svg>
`
}
"),
html = TRUE,
width = 400
),
target_pct = colDef(show = FALSE),
target_val = colDef(show = FALSE),
diff = colDef(show = FALSE),
min = colDef(show = FALSE),
max = colDef(show = FALSE)
)
)
browsable(tagList(
d3r::d3_dep_v7(),
tags$script(HTML("const scalex = d3.scaleLinear().range([40,360])")),
tags$div(
style = "width: 800px; background: white;",
rt
)
))