2022-05-11 23:27:40 +02:00
< template >
< div id = "heatmap" class = "w-full" >
< div class = "mx-auto" : style = "{ height: innerHeight + 160 + 'px', width: innerWidth + 52 + 'px' }" style = "background-color: rgba(13, 17, 23, 0)" >
< p class = "mb-2 px-1 text-sm text-gray-200" > { { Object . values ( daysListening ) . length } } listening sessions in the last year < / p >
< div class = "border border-opacity-25 rounded py-2 w-full" style = "background-color: #232323" : style = "{ height: innerHeight + 80 + 'px' }" >
< div : style = "{ width: innerWidth + 'px', height: innerHeight + 'px' }" class = "ml-10 mt-5 absolute" @mouseover ="mouseover" @mouseout ="mouseout" >
< div v-for ="dayLabel in dayLabels" :key="dayLabel.label" :style="dayLabel.style" class="absolute top-0 left-0 text-gray-300" > {{ dayLabel.label }} < / div >
< div v-for ="monthLabel in monthLabels" :key="monthLabel.id" :style="monthLabel.style" class="absolute top-0 left-0 text-gray-300" > {{ monthLabel.label }} < / div >
< div v -for = " ( block , index ) in data " :key ="block.dateString" :style ="block.style" :data-index ="index" class = "absolute top-0 left-0 h-2.5 w-2.5 rounded-sm" / >
< div class = "flex py-2 px-4" : style = "{ marginTop: innerHeight + 'px' }" >
< div class = "flex-grow" / >
< p style = "font-size: 10px; line-height: 10px" class = "text-gray-400 px-1" > Less < / p >
< div v -for = " block in legendBlocks " :key ="block.id" :style ="block.style" class = "h-2.5 w-2.5 rounded-sm" style = "margin-left: 1.5px; margin-right: 1.5px" / >
< p style = "font-size: 10px; line-height: 10px" class = "text-gray-400 px-1" > More < / p >
< / div >
< / div >
< / div >
< / div >
< / div >
< / template >
< script >
export default {
props : {
daysListening : {
type : Object ,
default : ( ) => { }
}
} ,
data ( ) {
return {
contentWidth : 0 ,
maxInnerWidth : 0 ,
innerHeight : 13 * 7 ,
blockWidth : 13 ,
data : [ ] ,
monthLabels : [ ] ,
tooltipEl : null ,
tooltipTextEl : null ,
tooltipArrowEl : null ,
showingTooltipIndex : - 1 ,
outlineColors : [ 'rgba(27, 31, 35, 0.06)' , 'rgba(255,255,255,0.03)' ] ,
bgColors : [ 'rgb(45,45,45)' , 'rgb(14, 68, 41)' , 'rgb(0, 109, 50)' , 'rgb(38, 166, 65)' , 'rgb(57, 211, 83)' ]
// GH Colors
// outlineColors: ['rgba(27, 31, 35, 0.06)', 'rgba(255,255,255,0.05)'],
// bgColors: ['rgb(22, 27, 34)', 'rgb(14, 68, 41)', 'rgb(0, 109, 50)', 'rgb(38, 166, 65)', 'rgb(57, 211, 83)']
}
} ,
computed : {
weeksToShow ( ) {
return Math . min ( 52 , Math . floor ( this . maxInnerWidth / this . blockWidth ) - 1 )
} ,
innerWidth ( ) {
return ( this . weeksToShow + 1 ) * 13
} ,
daysToShow ( ) {
return this . weeksToShow * 7 + this . dayOfWeekToday
} ,
dayOfWeekToday ( ) {
return new Date ( ) . getDay ( )
} ,
firstWeekStart ( ) {
return this . $addDaysToToday ( - this . daysToShow )
} ,
dayLabels ( ) {
return [
{
label : 'Mon' ,
style : {
transform : ` translate( ${ - 25 } px, ${ 13 } px) ` ,
lineHeight : '10px' ,
fontSize : '10px'
}
} ,
{
label : 'Wed' ,
style : {
transform : ` translate( ${ - 25 } px, ${ 13 * 3 } px) ` ,
lineHeight : '10px' ,
fontSize : '10px'
}
} ,
{
label : 'Fri' ,
style : {
transform : ` translate( ${ - 25 } px, ${ 13 * 5 } px) ` ,
lineHeight : '10px' ,
fontSize : '10px'
}
}
]
} ,
legendBlocks ( ) {
return [
{
id : 'legend-0' ,
style : ` background-color: ${ this . bgColors [ 0 ] } ;outline:1px solid ${ this . outlineColors [ 0 ] } ;outline-offset:-1px; `
} ,
{
id : 'legend-1' ,
style : ` background-color: ${ this . bgColors [ 1 ] } ;outline:1px solid ${ this . outlineColors [ 1 ] } ;outline-offset:-1px; `
} ,
{
id : 'legend-2' ,
style : ` background-color: ${ this . bgColors [ 2 ] } ;outline:1px solid ${ this . outlineColors [ 1 ] } ;outline-offset:-1px; `
} ,
{
id : 'legend-3' ,
style : ` background-color: ${ this . bgColors [ 3 ] } ;outline:1px solid ${ this . outlineColors [ 1 ] } ;outline-offset:-1px; `
} ,
{
id : 'legend-4' ,
style : ` background-color: ${ this . bgColors [ 4 ] } ;outline:1px solid ${ this . outlineColors [ 1 ] } ;outline-offset:-1px; `
}
]
}
} ,
methods : {
destroyTooltip ( ) {
if ( this . tooltipEl ) this . tooltipEl . remove ( )
this . tooltipEl = null
this . showingTooltipIndex = - 1
} ,
createTooltip ( ) {
const tooltip = document . createElement ( 'div' )
tooltip . className = 'absolute top-0 left-0 rounded bg-gray-500 text-white p-2 text-white max-w-xs pointer-events-none'
tooltip . style . display = 'none'
tooltip . id = 'heatmap-tooltip'
const tooltipText = document . createElement ( 'p' )
tooltipText . innerText = 'Tooltip'
tooltipText . style . fontSize = '10px'
tooltipText . style . lineHeight = '10px'
tooltip . appendChild ( tooltipText )
const tooltipArrow = document . createElement ( 'div' )
tooltipArrow . className = 'text-gray-500 arrow-down-small absolute -bottom-1 left-0 right-0 mx-auto'
tooltip . appendChild ( tooltipArrow )
this . tooltipEl = tooltip
this . tooltipTextEl = tooltipText
this . tooltipArrowEl = tooltipArrow
document . body . appendChild ( this . tooltipEl )
} ,
showTooltip ( index , block , rect ) {
if ( this . tooltipEl && this . showingTooltipIndex === index ) return
if ( ! this . tooltipEl ) {
this . createTooltip ( )
}
this . showingTooltipIndex = index
this . tooltipEl . style . display = 'block'
2022-05-12 00:35:04 +02:00
this . tooltipTextEl . innerHTML = block . value ? ` <strong> ${ this . $elapsedPretty ( block . value , true ) } listening</strong> on ${ block . datePretty } ` : ` No listening sessions on ${ block . datePretty } `
2022-05-11 23:27:40 +02:00
const calculateRect = this . tooltipEl . getBoundingClientRect ( )
const w = calculateRect . width / 2
var left = rect . x - w
var offsetX = 0
if ( left < 0 ) {
offsetX = Math . abs ( left )
left = 0
} else if ( rect . x + w > window . innerWidth - 10 ) {
offsetX = window . innerWidth - 10 - ( rect . x + w )
left += offsetX
}
this . tooltipEl . style . transform = ` translate( ${ left } px, ${ rect . y - 32 } px) `
this . tooltipArrowEl . style . transform = ` translate( ${ 5 - offsetX } px, 0px) `
} ,
hideTooltip ( ) {
if ( this . showingTooltipIndex >= 0 && this . tooltipEl ) {
this . tooltipEl . style . display = 'none'
this . showingTooltipIndex = - 1
}
} ,
mouseover ( e ) {
if ( isNaN ( e . target . dataset . index ) ) {
this . hideTooltip ( )
return
}
var block = this . data [ e . target . dataset . index ]
var rect = e . target . getBoundingClientRect ( )
this . showTooltip ( e . target . dataset . index , block , rect )
} ,
mouseout ( e ) {
this . hideTooltip ( )
} ,
buildData ( ) {
this . data = [ ]
var maxValue = 0
var minValue = 0
Object . values ( this . daysListening ) . forEach ( ( val ) => {
if ( val > maxValue ) maxValue = val
if ( ! minValue || val < minValue ) minValue = val
} )
const range = maxValue - minValue + 0.01
for ( let i = 0 ; i < this . daysToShow + 1 ; i ++ ) {
const col = Math . floor ( i / 7 )
const row = i % 7
const date = i === 0 ? this . firstWeekStart : this . $addDaysToDate ( this . firstWeekStart , i )
const dateString = this . $formatJsDate ( date , 'yyyy-MM-dd' )
const datePretty = this . $formatJsDate ( date , 'MMM d, yyyy' )
const monthString = this . $formatJsDate ( date , 'MMM' )
const value = this . daysListening [ dateString ] || 0
const x = col * 13
const y = row * 13
var bgColor = this . bgColors [ 0 ]
var outlineColor = this . outlineColors [ 0 ]
if ( value ) {
outlineColor = this . outlineColors [ 1 ]
var percentOfAvg = ( value - minValue ) / range
var bgIndex = Math . floor ( percentOfAvg * 4 ) + 1
bgColor = this . bgColors [ bgIndex ] || 'red'
}
this . data . push ( {
date ,
dateString ,
datePretty ,
monthString ,
dayOfMonth : Number ( dateString . split ( '-' ) . pop ( ) ) ,
yearString : dateString . split ( '-' ) . shift ( ) ,
value ,
col ,
row ,
style : ` transform:translate( ${ x } px, ${ y } px);background-color: ${ bgColor } ;outline:1px solid ${ outlineColor } ;outline-offset:-1px; `
} )
}
console . log ( 'Data' , this . data )
this . monthLabels = [ ]
var lastMonth = null
for ( let i = 0 ; i < this . data . length ; i ++ ) {
if ( this . data [ i ] . monthString !== lastMonth ) {
const weekOfMonth = Math . floor ( this . data [ i ] . dayOfMonth / 7 )
if ( weekOfMonth <= 2 ) {
this . monthLabels . push ( {
id : this . data [ i ] . dateString + '-ml' ,
label : this . data [ i ] . monthString ,
style : {
transform : ` translate( ${ this . data [ i ] . col * 13 } px, -15px) ` ,
lineHeight : '10px' ,
fontSize : '10px'
}
} )
lastMonth = this . data [ i ] . monthString
}
}
}
} ,
init ( ) {
const heatmapEl = document . getElementById ( 'heatmap' )
this . contentWidth = heatmapEl . clientWidth
this . maxInnerWidth = this . contentWidth - 52
this . buildData ( )
}
} ,
updated ( ) { } ,
mounted ( ) {
this . init ( )
} ,
beforeDestroy ( ) { }
}
< / script >