MediaWiki:Gadget-wix-interactive.js: Difference between revisions
Content deleted Content added
No edit summary |
No edit summary |
||
Line 24:
'ifrs-timeline': initIfrsTimeline,
'liability-waterfall': initLiabilityWaterfall,
'prob-weighted': initProbWeighted,
'discount-rate': initDiscountRate
};
Line 1,704 ⟶ 1,705:
slider.addEventListener( 'input', update );
// Initial render
update();
}
/* ================================================================
DISCOUNT RATE
Discount-rate dashboard: shows how discounting reduces a
future claim to present value, with year-by-year unwinding.
================================================================ */
function initDiscountRate( container ) {
/* ── Constants ────────────────────────────────────────────── */
var CLAIM = 100000;
var YEARS = 5;
/* ── Colors (WIX tokens) ─────────────────────────────────── */
var styles = getComputedStyle( container );
var colorPV = styles.getPropertyValue( '--wix-accent' ).trim() || '#1a5276';
var colorAccr = styles.getPropertyValue( '--wix-correct' ).trim() || '#1e8449';
var colorGrid = styles.getPropertyValue( '--wix-border-subtle' ).trim() || '#eaecf0';
var colorAxis = styles.getPropertyValue( '--wix-text-muted' ).trim() || '#54595d';
var colorNom = styles.getPropertyValue( '--wix-border' ).trim() || '#c8ccd1';
/* ── Helpers ─────────────────────────────────────────────── */
function pv( fv, r, t ) {
return fv / Math.pow( 1 + r, t );
}
function fmt( v ) {
return '\u20AC' + wix.formatNumber( Math.round( v ) );
}
function fmtK( v ) {
return '\u20AC' + Math.round( v / 1000 ) + 'k';
}
/* ── Build UI ────────────────────────────────────────────── */
wix.empty( container );
var wrapper = wix.el( 'div', { className: 'wix-eng-wrapper' } );
container.appendChild( wrapper );
// Slider row (reuse insurer-engines pattern)
var slider = wix.el( 'input', {
type: 'range', min: '0', max: '6', step: '0.1', value: '3'
} );
var sliderVal = wix.el( 'div', { className: 'wix-eng-slider-val', textContent: '3.0%' } );
wrapper.appendChild( wix.el( 'div', { className: 'wix-eng-slider-row' }, [
wix.el( 'div', { className: 'wix-eng-slider-label', textContent: 'Discount rate' } ),
slider,
sliderVal
] ) );
// Stat cards (3-column grid, reuse reserve-sensitivity pattern)
var elPV = wix.el( 'div', { className: 'wix-rs-card-num' } );
var elOver = wix.el( 'div', { className: 'wix-rs-card-num' } );
wrapper.appendChild( wix.el( 'div', { className: 'wix-dr-cards' }, [
wix.el( 'div', { className: 'wix-rs-card' }, [
wix.el( 'div', { className: 'wix-rs-card-label', textContent: 'Claim (nominal)' } ),
wix.el( 'div', { className: 'wix-rs-card-num wix-rs-card-num--muted', textContent: '\u20AC100,000' } )
] ),
wix.el( 'div', { className: 'wix-rs-card' }, [
wix.el( 'div', { className: 'wix-rs-card-label', textContent: 'Present value today' } ),
elPV
] ),
wix.el( 'div', { className: 'wix-rs-card' }, [
wix.el( 'div', { className: 'wix-rs-card-label', textContent: 'Overstatement if no discount' } ),
elOver
] )
] ) );
// Section label
wrapper.appendChild( wix.el( 'div', { className: 'wix-eng-section-label', textContent: 'Liability unwinding \u2014 year by year' } ) );
// Chart canvas
var canvas = wix.el( 'canvas' );
wrapper.appendChild( wix.el( 'div', { className: 'wix-dr-chart' }, [ canvas ] ) );
// Legend
wrapper.appendChild( wix.el( 'div', { className: 'wix-pm-legend' }, [
wix.el( 'span', { className: 'wix-pm-legend-item' }, [
wix.el( 'span', { className: 'wix-pm-swatch', style: { background: colorPV } } ),
'Present value'
] ),
wix.el( 'span', { className: 'wix-pm-legend-item' }, [
wix.el( 'span', { className: 'wix-pm-swatch', style: { background: colorAccr } } ),
'Annual accretion'
] ),
wix.el( 'span', { className: 'wix-pm-legend-item' }, [
wix.el( 'span', { className: 'wix-dr-nom-swatch' } ),
'Nominal (\u20AC100,000)'
] )
] ) );
// Table
var tableEl = wix.el( 'div', { className: 'wix-dr-table' } );
wrapper.appendChild( tableEl );
/* ── Chart Drawing ───────────────────────────────────────── */
function drawChart( pvs, accretions ) {
var ctx = canvas.getContext( '2d' );
var dpr = window.devicePixelRatio || 1;
var rect = canvas.getBoundingClientRect();
if ( rect.width === 0 || rect.height === 0 ) {
return;
}
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.scale( dpr, dpr );
var w = rect.width;
var h = rect.height;
var pad = { t: 12, b: 28, l: 50, r: 12 };
var cw = w - pad.l - pad.r;
var ch = h - pad.t - pad.b;
var yMax = 105000;
var n = YEARS + 1;
ctx.clearRect( 0, 0, w, h );
// Y helper
function yPos( v ) {
return pad.t + ch - ( ch * v / yMax );
}
function xPos( i ) {
return pad.l + ( cw * i / ( n - 1 ) );
}
// Grid + Y labels
ctx.strokeStyle = colorGrid;
ctx.lineWidth = 0.5;
ctx.fillStyle = colorAxis;
ctx.font = '11px system-ui, sans-serif';
ctx.textAlign = 'right';
var gridSteps = 5;
var i, y;
for ( i = 0; i <= gridSteps; i++ ) {
var gv = yMax * i / gridSteps;
y = yPos( gv );
ctx.beginPath();
ctx.moveTo( pad.l, y );
ctx.lineTo( pad.l + cw, y );
ctx.stroke();
ctx.fillText( fmtK( gv ), pad.l - 6, y + 4 );
}
// X labels
ctx.textAlign = 'center';
for ( i = 0; i < n; i++ ) {
ctx.fillText( 'Year ' + i, xPos( i ), h - 6 );
}
// Nominal dashed line
ctx.strokeStyle = colorNom;
ctx.lineWidth = 1;
ctx.setLineDash( [ 4, 4 ] );
ctx.beginPath();
ctx.moveTo( pad.l, yPos( CLAIM ) );
ctx.lineTo( pad.l + cw, yPos( CLAIM ) );
ctx.stroke();
ctx.setLineDash( [] );
// Accretion bars
var barWidth = Math.min( 30, cw / n * 0.45 );
for ( i = 0; i < n; i++ ) {
if ( accretions[ i ] <= 0 ) {
continue;
}
var bx = xPos( i ) - barWidth / 2;
var by = yPos( accretions[ i ] );
var bh = yPos( 0 ) - by;
ctx.fillStyle = colorAccr + '66';
ctx.fillRect( bx, by, barWidth, bh );
ctx.strokeStyle = colorAccr;
ctx.lineWidth = 1;
ctx.strokeRect( bx, by, barWidth, bh );
}
// PV line
ctx.beginPath();
ctx.strokeStyle = colorPV;
ctx.lineWidth = 2;
for ( i = 0; i < n; i++ ) {
var px = xPos( i );
var py = yPos( pvs[ i ] );
if ( i === 0 ) {
ctx.moveTo( px, py );
} else {
ctx.lineTo( px, py );
}
}
ctx.stroke();
// PV dots
for ( i = 0; i < n; i++ ) {
ctx.beginPath();
ctx.arc( xPos( i ), yPos( pvs[ i ] ), 4, 0, Math.PI * 2 );
ctx.fillStyle = colorPV;
ctx.fill();
}
}
/* ── Table ───────────────────────────────────────────────── */
function buildTable( pvs, accretions ) {
wix.empty( tableEl );
var thead = wix.el( 'tr', {}, [
wix.el( 'th', { textContent: 'Year' } ),
wix.el( 'th', { textContent: 'Remaining' } ),
wix.el( 'th', { textContent: 'Present value' } ),
wix.el( 'th', { textContent: 'Accretion' } )
] );
var table = wix.el( 'table', { className: 'wix-dr-tbl' }, [ thead ] );
for ( var yr = 0; yr <= YEARS; yr++ ) {
table.appendChild( wix.el( 'tr', {}, [
wix.el( 'td', { textContent: 'Year ' + yr } ),
wix.el( 'td', { textContent: ( YEARS - yr ) + ' yr' } ),
wix.el( 'td', { textContent: fmt( pvs[ yr ] ) } ),
wix.el( 'td', { textContent: yr === 0 ? '\u2014' : fmt( accretions[ yr ] ) } )
] ) );
}
tableEl.appendChild( table );
}
/* ── Update ──────────────────────────────────────────────── */
function update() {
var r = parseFloat( slider.value ) / 100;
sliderVal.textContent = slider.value + '%';
var todayPV = pv( CLAIM, r, YEARS );
elPV.textContent = fmt( todayPV );
elOver.textContent = fmt( CLAIM - todayPV );
var pvs = [];
var accretions = [];
for ( var yr = 0; yr <= YEARS; yr++ ) {
var remaining = YEARS - yr;
var val = pv( CLAIM, r, remaining );
pvs.push( Math.round( val ) );
accretions.push( yr === 0 ? 0 : Math.round( val - pv( CLAIM, r, remaining + 1 ) ) );
}
drawChart( pvs, accretions );
buildTable( pvs, accretions );
}
/* ── Event Wiring ────────────────────────────────────────── */
slider.addEventListener( 'input', update );
window.addEventListener( 'resize', function () {
update();
} );
// Initial render
| |||