MediaWiki:Gadget-wix-interactive.js: Difference between revisions
Content deleted Content added
No edit summary |
No edit summary |
||
Line 12:
- "risk-adjustment" Brittany storm confidence-level risk adjustment chart
- "grouping-funnel" 3-step IFRS 17 contract grouping walkthrough
- "csm-rollforward" Horizontal waterfall: CSM opening → movements → closing
================================================================ */
Line 30 ⟶ 31:
'balance-sheet': initBalanceSheet,
'risk-adjustment': initRiskAdjustment,
'grouping-funnel': initGroupingFunnel,
'csm-rollforward': initCsmRollforward
};
Line 2,705 ⟶ 2,707:
render();
}
/* ================================================================
CSM ROLLFORWARD — data-wix-module="csm-rollforward"
Horizontal waterfall chart: FY24 opening balance through six
movement items to FY25 closing balance. Sliders let the user
adjust each component; click any bar for an explanation.
================================================================ */
function initCsmRollforward( container ) {
/* ── Item definitions ────────────────────────────────────── */
var ITEMS = [
{ key: 'nb', label: 'New business CSM', val: 2199, min: 0, max: 5000, step: 50, color: '#1D9E75',
explain: 'The CSM recognised at inception of new insurance contracts written during the year. It represents the present value of future unearned profit that the insurer expects to earn from these new policies. A higher figure signals strong commercial momentum.' },
{ key: 'roi', label: 'Return on inforce', val: 1328, min: 0, max: 3000, step: 50, color: '#378ADD',
explain: 'Sometimes called the "unwind of discount rate", this is the interest accretion on the opening CSM balance. Because future cash flows were discounted at inception, the passage of time increases their present value. Think of it as the CSM earning a return simply by getting one year closer to settlement.' },
{ key: 'rel', label: 'CSM release', val: -2954, min: -5000, max: 0, step: 50, color: '#D85A30',
explain: 'The portion of CSM released to the income statement as profit in the period. Under IFRS 17, profit is recognised as services are delivered to policyholders \u2014 so this is the primary mechanism that turns the CSM balance into reported earnings. It is always negative in the rollforward because it reduces the remaining stock of unearned profit.' },
{ key: 'eco', label: 'Economic variance', val: 594, min: -2000, max: 2000, step: 50, color: '#534AB7',
explain: 'Changes in the CSM caused by movements in financial assumptions \u2014 interest rates, equity markets, credit spreads, and foreign exchange. Under the Variable Fee Approach (VFA), changes in the insurer\u2019s share of underlying asset returns adjust the CSM rather than hitting P&L directly. Positive values mean financial conditions improved for the insurer.' },
{ key: 'opv', label: 'Operating variance', val: -316, min: -2000, max: 2000, step: 50, color: '#D4537E',
explain: 'Changes in the CSM driven by updates to non-financial (operating) assumptions \u2014 mortality, morbidity, lapse rates, and expenses. When actual experience or updated projections differ from what was assumed at inception, the CSM absorbs the difference. Negative means experience or updated assumptions were worse than expected.' },
{ key: 'oth', label: 'Other', val: -1451, min: -3000, max: 1000, step: 50, color: '#888780',
explain: 'A catch-all for items such as foreign exchange translation effects on non-euro subsidiaries, scope changes (acquisitions or disposals of portfolios), model or methodology changes, and any other adjustments that don\u2019t fit neatly into the categories above.' }
];
var FY24 = 33853;
var TOTAL_COLOR = '#3266ad';
var TOTALS_EXPLAIN = 'The opening (FY24) and closing (FY25) CSM balance represents the total stock of unearned profit the insurer expects to recognise in future periods. The rollforward reconciles how the balance moved from one year-end to the next through the components shown above.';
/* ── Live values ─────────────────────────────────────────── */
var vals = {};
ITEMS.forEach( function ( it ) { vals[it.key] = it.val; } );
/* ── Formatting ──────────────────────────────────────────── */
function fmt( v ) {
return ( v < 0 ? '-' : '' ) + '\u20AC' + Math.abs( Math.round( v ) ).toLocaleString() + 'm';
}
/* ── Chart colors (grid / axis) ──────────────────────────── */
var styles = getComputedStyle( container );
var colorGrid = styles.getPropertyValue( '--wix-border-subtle' ).trim() || '#eaecf0';
var colorAxis = styles.getPropertyValue( '--wix-text-muted' ).trim() || '#54595d';
var colorText = styles.getPropertyValue( '--wix-text' ).trim() || '#202122';
/* ── Build UI ────────────────────────────────────────────── */
wix.empty( container );
var wrapper = wix.el( 'div', { className: 'wix-eng-wrapper' } );
container.appendChild( wrapper );
/* Slider grid */
var sliderGrid = wix.el( 'div', { className: 'wix-csm-sliders' } );
var sliderRefs = {};
var valRefs = {};
ITEMS.forEach( function ( it ) {
var sl = wix.el( 'input', { type: 'range', min: String( it.min ), max: String( it.max ),
step: String( it.step ), value: String( it.val ) } );
var vl = wix.el( 'span', { className: 'wix-csm-slider-val', textContent: it.val.toLocaleString() } );
sliderRefs[it.key] = sl;
valRefs[it.key] = vl;
sliderGrid.appendChild( wix.el( 'div', { className: 'wix-csm-slider-group' }, [
wix.el( 'label', { textContent: it.label } ),
wix.el( 'div', { className: 'wix-csm-slider-row' }, [ sl, vl ] )
] ) );
} );
wrapper.appendChild( sliderGrid );
/* Chart */
var canvas = wix.el( 'canvas' );
var chartWrap = wix.el( 'div', { className: 'wix-csm-chart' }, [ canvas ] );
wrapper.appendChild( chartWrap );
/* Explainer */
var exDot = wix.el( 'span', { className: 'wix-csm-ex-dot' } );
var exTitle = wix.el( 'span', { className: 'wix-csm-ex-title' } );
var exVal = wix.el( 'span', { className: 'wix-csm-ex-val' } );
var exBody = wix.el( 'p', { className: 'wix-csm-ex-body' } );
var explainer = wix.el( 'div', { className: 'wix-csm-explainer wix-csm-explainer--hidden' }, [
wix.el( 'div', { className: 'wix-csm-ex-head' }, [ exDot, exTitle, exVal ] ),
exBody
] );
wrapper.appendChild( explainer );
/* Hint */
var hint = wix.el( 'p', { className: 'wix-csm-hint', textContent: 'Click any bar for an explanation of that component.' } );
wrapper.appendChild( hint );
/* ── Waterfall data ──────────────────────────────────────── */
function getRows() {
var rows = [];
rows.push( { label: 'FY24', val: FY24, color: TOTAL_COLOR, isTotal: true } );
var running = FY24;
ITEMS.forEach( function ( it ) {
var v = vals[it.key];
rows.push( { label: it.label, val: v, color: it.color, base: v >= 0 ? running : running + v } );
running += v;
} );
rows.push( { label: 'FY25', val: running, color: TOTAL_COLOR, isTotal: true } );
return rows;
}
/* ── Hit regions for click ───────────────────────────────── */
var hitBoxes = [];
/* ── Drawing ─────────────────────────────────────────────── */
function draw() {
var rows = getRows();
var numBars = rows.length;
var dpr = window.devicePixelRatio || 1;
var rect = chartWrap.getBoundingClientRect();
var W = rect.width;
if ( W === 0 ) return;
var BAR_H = 38;
var GAP = 10;
var totalH = numBars * ( BAR_H + GAP ) + 40;
chartWrap.style.height = totalH + 'px';
canvas.width = W * dpr;
canvas.height = totalH * dpr;
var ctx = canvas.getContext( '2d' );
ctx.scale( dpr, dpr );
ctx.clearRect( 0, 0, W, totalH );
/* Compute axis range */
var allVals = rows.map( function ( r ) {
if ( r.isTotal ) return r.val;
return r.base;
} );
allVals = allVals.concat( rows.map( function ( r ) {
if ( r.isTotal ) return r.val;
return r.base + Math.abs( r.val );
} ) );
var dataMax = Math.max.apply( null, allVals ) * 1.12;
var dataMin = 0;
var padL = 130, padR = 16, padT = 6, padB = 30;
var cw = W - padL - padR;
function tx( v ) { return padL + ( v - dataMin ) / ( dataMax - dataMin ) * cw; }
/* Grid lines */
ctx.font = '11px sans-serif';
ctx.textAlign = 'center';
var step = 5000;
if ( dataMax > 50000 ) step = 10000;
if ( dataMax < 15000 ) step = 2000;
for ( var g = 0; g <= dataMax; g += step ) {
var gx = tx( g );
ctx.strokeStyle = colorGrid;
ctx.lineWidth = 0.5;
ctx.beginPath(); ctx.moveTo( gx, padT ); ctx.lineTo( gx, totalH - padB ); ctx.stroke();
ctx.fillStyle = colorAxis;
ctx.fillText( fmt( g ), gx, totalH - padB + 16 );
}
/* Bars */
hitBoxes = [];
rows.forEach( function ( r, i ) {
var y = padT + i * ( BAR_H + GAP );
var barBase = r.isTotal ? 0 : r.base;
var barVal = r.isTotal ? r.val : Math.abs( r.val );
var x0 = tx( barBase );
var x1 = tx( barBase + barVal );
var bw = Math.max( x1 - x0, 2 );
/* Row label */
ctx.fillStyle = colorText;
ctx.font = '12px sans-serif';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
ctx.fillText( r.label, padL - 10, y + BAR_H / 2 );
/* Bar */
ctx.fillStyle = r.color;
ctx.beginPath();
ctx.roundRect( x0, y, bw, BAR_H, 3 );
ctx.fill();
/* Value label on bar */
var valText = fmt( r.isTotal ? r.val : r.val );
ctx.fillStyle = '#fff';
ctx.font = '500 11px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
if ( bw > 60 ) {
ctx.fillText( valText, x0 + bw / 2, y + BAR_H / 2 );
} else {
ctx.fillStyle = r.color;
ctx.textAlign = 'left';
ctx.fillText( valText, x0 + bw + 4, y + BAR_H / 2 );
}
/* Connector line to next row */
if ( !r.isTotal && i < rows.length - 1 ) {
var endX = tx( barBase + ( r.val >= 0 ? Math.abs( r.val ) : 0 ) );
ctx.strokeStyle = colorGrid;
ctx.lineWidth = 1;
ctx.setLineDash( [ 3, 3 ] );
ctx.beginPath();
ctx.moveTo( endX, y + BAR_H );
ctx.lineTo( endX, y + BAR_H + GAP );
ctx.stroke();
ctx.setLineDash( [] );
}
/* Hit box */
hitBoxes.push( { x: x0, y: y, w: bw, h: BAR_H, idx: i } );
} );
}
/* ── Click → explainer ───────────────────────────────────── */
canvas.addEventListener( 'click', function ( e ) {
var rect = canvas.getBoundingClientRect();
var dpr = window.devicePixelRatio || 1;
var mx = ( e.clientX - rect.left );
var my = ( e.clientY - rect.top );
for ( var i = 0; i < hitBoxes.length; i++ ) {
var b = hitBoxes[i];
if ( mx >= b.x && mx <= b.x + b.w && my >= b.y && my <= b.y + b.h ) {
var idx = b.idx;
if ( idx === 0 || idx === ITEMS.length + 1 ) {
exDot.style.background = TOTAL_COLOR;
exTitle.textContent = idx === 0 ? 'FY24 opening balance' : 'FY25 closing balance';
exVal.textContent = idx === 0 ? fmt( FY24 ) : fmt( getRows()[ITEMS.length + 1].val );
exBody.textContent = TOTALS_EXPLAIN;
} else {
var item = ITEMS[idx - 1];
exDot.style.background = item.color;
exTitle.textContent = item.label;
exVal.textContent = fmt( vals[item.key] );
exBody.textContent = item.explain;
}
explainer.className = 'wix-csm-explainer';
hint.style.display = 'none';
return;
}
}
} );
/* ── Slider events ───────────────────────────────────────── */
ITEMS.forEach( function ( it ) {
sliderRefs[it.key].addEventListener( 'input', function () {
vals[it.key] = Number( sliderRefs[it.key].value );
valRefs[it.key].textContent = vals[it.key].toLocaleString();
draw();
} );
} );
/* ── Resize ──────────────────────────────────────────────── */
window.addEventListener( 'resize', draw );
draw();
}
| |||