|
( function( w ) { |
|
var day_ms = 86400000; |
|
|
|
var config = { |
|
origin : 365 * day_ms, // 1971-01-01 |
|
offset : 2419200000, // 28 days |
|
scale : 4838400000, // 56 days |
|
decay : 0.5 |
|
}; |
|
|
|
var scorer = { |
|
_distance: function( val ) { |
|
return Math.max( 0, Math.abs( val - config.origin ) - config.offset ); |
|
}, |
|
gauss: function( val ) { |
|
var segma_sq = -1 * Math.pow( config.scale, 2 ) / ( 2 * Math.log( config.decay ) ); |
|
return Math.exp( -1 * ( Math.pow( scorer._distance( val ), 2 ) / ( 2 * segma_sq ) ) ) |
|
}, |
|
exp: function( val ) { |
|
var lambda = Math.log( config.decay ) / config.scale; |
|
return Math.exp( lambda * scorer._distance( val ) ); |
|
}, |
|
linear: function( val ) { |
|
var s = config.scale / ( 1 - config.decay ); |
|
return Math.max( ( ( s - scorer._distance( val ) ) / s ), 0 ); |
|
} |
|
} |
|
|
|
w.day_scorer = { |
|
set_configs: function( offset_days, scale_days, decay ) { |
|
config.offset = day_ms * offset_days; |
|
config.scale = day_ms * scale_days; |
|
config.decay = decay; |
|
}, |
|
|
|
get_configs: function() { |
|
return { |
|
offset : config.offset / day_ms, |
|
scale : config.scale / day_ms, |
|
decay : config.decay, |
|
} |
|
}, |
|
|
|
// Calc scores for range of days defaults to a year |
|
calc_days: function( first, last ) { |
|
// Origin is always last day of range |
|
config.origin = last * day_ms; |
|
|
|
var s = { |
|
gauss : [], |
|
exp : [], |
|
linear : [] |
|
}; |
|
|
|
for ( var d = first; d <= last; d++ ) { |
|
s.gauss.push( scorer.gauss( d * day_ms ) ); |
|
s.exp.push( scorer.exp( d * day_ms ) ); |
|
s.linear.push( scorer.linear( d * day_ms ) ); |
|
} |
|
|
|
return s; |
|
} |
|
}; |
|
} )( window ); |
|
|
|
( function( w, d ) { |
|
// Set default chart configs |
|
Chart.defaults.global.responsive = true; |
|
Chart.defaults.global.scaleOverride = true; |
|
Chart.defaults.global.scaleSteps = 4; |
|
Chart.defaults.global.scaleStepWidth = 0.25; |
|
Chart.defaults.global.scaleStartValue = 0; |
|
Chart.defaults.global.tooltipXOffset = 0; |
|
Chart.defaults.global.multiTooltipTemplate = "<%= value.toFixed(3) %><%if (datasetLabel){%> - <%=datasetLabel%><%}%>"; |
|
|
|
var first = 0, |
|
last = 365; |
|
|
|
var get_labels = function( all ) { |
|
var months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; |
|
var labels = []; |
|
|
|
for ( var day = first; day <= last; day++ ) { |
|
var date = new Date( 86400000 * day ); |
|
|
|
if ( all || date.getUTCDate() == 1 ) { |
|
labels.push( months[ date.getUTCMonth() ] + ' ' + date.getUTCDate() ); |
|
} else { |
|
labels.push( '' ); |
|
} |
|
} |
|
|
|
return labels; |
|
}; |
|
|
|
var get_filler = function() { |
|
var filler = new Array( 1 + last - first ); |
|
filler.fill( 0 ); |
|
|
|
return filler; |
|
}; |
|
|
|
var update_chart = function() { |
|
var scores = w.day_scorer.calc_days( first, last ); |
|
var labels = get_labels( true ); |
|
|
|
[ 'gauss', 'exp', 'linear' ].forEach( function( type, i ) { |
|
scores[ type ].forEach( function( s, j ) { |
|
graph.datasets[ i ].points[ j ].value = s; |
|
graph.datasets[ i ].points[ j ].label = labels[ j ]; |
|
} ); |
|
} ); |
|
|
|
graph.update() |
|
} |
|
|
|
var graph = null, |
|
ctx = null, |
|
data = { |
|
labels: get_labels( false ), |
|
datasets: [ |
|
{ |
|
label: "gauss", |
|
fillColor: "rgba( 0, 135, 190, 0.05 )", |
|
strokeColor: "rgba( 0, 135, 190, 1 )", |
|
pointColor: "rgba( 0, 135, 190, 1)", |
|
data: get_filler() |
|
}, |
|
{ |
|
label: "exp", |
|
fillColor: "rgba( 213, 78, 33, 0.05 )", |
|
strokeColor: "rgba( 213, 78, 33, 1 )", |
|
pointColor: "rgba( 213, 78, 33, 1 )", |
|
data: get_filler() |
|
}, |
|
{ |
|
label: "linear", |
|
fillColor: "rgba( 168, 190, 206, 0.05 )", |
|
strokeColor: "rgba( 168, 190, 206, 1 )", |
|
pointColor: "rgba( 168, 190, 206, 1 )", |
|
data: get_filler() |
|
} |
|
] |
|
}, |
|
options = { |
|
scaleShowVerticalLines: false, |
|
pointDot: false, |
|
pointHitDetectionRadius: 1 |
|
}, |
|
ids = { |
|
chart : '', |
|
offset : '', |
|
scale : '', |
|
decay : '' |
|
}; |
|
|
|
w.decay_chart = { |
|
init: function( chart_id, offset_id, scale_id, decay_id ) { |
|
// Store IDs |
|
ids = { |
|
chart : chart_id, |
|
offset : offset_id, |
|
scale : scale_id, |
|
decay : decay_id |
|
} |
|
|
|
// Set UI to default values |
|
var configs = w.day_scorer.get_configs(); |
|
d.getElementById( ids.offset ).value = configs.offset; |
|
d.getElementById( ids.scale ).value = configs.scale; |
|
d.getElementById( ids.decay ).value = configs.decay; |
|
|
|
ctx = d.getElementById( ids.chart ).getContext( "2d" ); |
|
graph = new Chart( ctx ).Line( data, options ); |
|
|
|
update_chart(); |
|
}, |
|
update: function() { |
|
// Check new values from UI |
|
var valid = true; |
|
[ ids.offset, ids.scale, ids.decay ].forEach( function( id ) { |
|
var elem = d.getElementById( id ); |
|
if ( false == elem.validity.valid ) { |
|
valid = false; |
|
|
|
if ( 'function' == typeof elem.reportValidity ) { |
|
elem.reportValidity(); |
|
} |
|
|
|
elem.className = "invalid"; |
|
} else { |
|
elem.className = ""; |
|
} |
|
} ); |
|
|
|
if ( !valid ) { |
|
return; |
|
} |
|
|
|
w.day_scorer.set_configs( |
|
parseInt( d.getElementById( ids.offset ).value ), |
|
parseInt( d.getElementById( ids.scale ).value ), |
|
parseFloat( d.getElementById( ids.decay ).value ) |
|
); |
|
|
|
update_chart(); |
|
} |
|
} |
|
} )( window, document ); |
|
|
|
window.onload = function() { |
|
var elem = document.getElementById( 'chart_control' ); |
|
elem.addEventListener( 'submit', function( event ) { |
|
event.preventDefault(); |
|
window.decay_chart.update(); |
|
} ); |
|
elem.style.display = ''; |
|
|
|
window.decay_chart.init( 'chart', 'offset', 'scale', 'decay' ); |
|
} |