User:A2569875/Module:Graph/ChartPolar.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
// Module:Graph/ChartPolar.js
( function () {
'use strict';
window.mw = window.mw || {};
mw.libs = mw.libs || {};
var GW = mw.libs.GraphWidget = mw.libs.GraphWidget || {};
GW.Renderers = GW.Renderers || {};
var DEG2RAD = Math.PI / 180;
function polarToCart( cx, cy, r, angleRad ) {
return [
cx + r * Math.cos( angleRad ),
cy + r * Math.sin( angleRad )
];
}
// 建立扇形或環形扇區 SVG Path (r0 = 0 為實心扇形,r0 > 0 為環形扇區)
function buildSectorPath( cx, cy, r0, r1, a0, a1 ) {
var span = a1 - a0;
if ( Math.abs( span ) >= Math.PI * 2 - 1e-4 ) {
var midA = a0 + Math.PI;
return buildSectorPath( cx, cy, r0, r1, a0, midA ) + ' ' + buildSectorPath( cx, cy, r0, r1, midA, a1 );
}
var largeArc = Math.abs( span ) > Math.PI ? 1 : 0;
var sweepOut = span >= 0 ? 1 : 0;
var sweepIn = span >= 0 ? 0 : 1;
var pOut0 = polarToCart( cx, cy, r1, a0 );
var pOut1 = polarToCart( cx, cy, r1, a1 );
if ( r0 <= 0.5 ) {
return [
'M', cx.toFixed( 2 ) + ',' + cy.toFixed( 2 ),
'L', pOut0[ 0 ].toFixed( 2 ) + ',' + pOut0[ 1 ].toFixed( 2 ),
'A', r1.toFixed( 2 ) + ',' + r1.toFixed( 2 ), '0', largeArc + ',' + sweepOut, pOut1[ 0 ].toFixed( 2 ) + ',' + pOut1[ 1 ].toFixed( 2 ),
'Z'
].join( ' ' );
}
var pIn1 = polarToCart( cx, cy, r0, a1 );
var pIn0 = polarToCart( cx, cy, r0, a0 );
return [
'M', pOut0[ 0 ].toFixed( 2 ) + ',' + pOut0[ 1 ].toFixed( 2 ),
'A', r1.toFixed( 2 ) + ',' + r1.toFixed( 2 ), '0', largeArc + ',' + sweepOut, pOut1[ 0 ].toFixed( 2 ) + ',' + pOut1[ 1 ].toFixed( 2 ),
'L', pIn1[ 0 ].toFixed( 2 ) + ',' + pIn1[ 1 ].toFixed( 2 ),
'A', r0.toFixed( 2 ) + ',' + r0.toFixed( 2 ), '0', largeArc + ',' + sweepIn, pIn0[ 0 ].toFixed( 2 ) + ',' + pIn0[ 1 ].toFixed( 2 ),
'Z'
].join( ' ' );
}
// =========================================================================
// 1. 圓餅圖與甜甜圈圖 (pie / donut)
// =========================================================================
function renderPieChart( container, spec, rows, meta ) {
var U = GW.Utils;
if ( !U || !Array.isArray( rows ) || rows.length === 0 ) {
return false;
}
var xField = spec.xField || 'x';
var yField = spec.yField || 'y';
var first = rows[ 0 ] || {};
if ( first[ xField ] === undefined ) {
xField = first._xfield !== undefined ? '_xfield' : ( first.label !== undefined ? 'label' : ( first.date !== undefined ? 'date' : 'x' ) );
}
if ( first[ yField ] === undefined ) {
yField = first._yfield !== undefined ? '_yfield' : ( first.views !== undefined ? 'views' : ( first.value !== undefined ? 'value' : 'y' ) );
}
var items = [];
var totalVal = 0;
for ( var i = 0; i < rows.length; i++ ) {
var v = Math.max( 0, Number( rows[ i ][ yField ] ) || 0 );
if ( v > 0 ) {
var lbl = String( rows[ i ][ xField ] !== undefined ? rows[ i ][ xField ] : ( '#' + ( i + 1 ) ) );
items.push( { label: lbl, value: v } );
totalVal += v;
}
}
if ( items.length === 0 || totalVal <= 0 ) {
return false;
}
var plotW = Math.max( 140, Number( spec.width ) || 320 );
var plotH = Math.max( 140, Number( spec.height ) || 240 );
var showLegend = spec.legend !== undefined && spec.legend !== null && spec.legend !== false && spec.legend !== 'false';
var legW = showLegend ? 135 : 0;
var padT = ( spec.title || ( meta && meta.notice ) ) ? 32 : 14;
var padB = 16;
var totalW = plotW + legW + 24;
var totalH = Math.max( padT + plotH + padB, padT + ( showLegend ? items.length * 18 + 28 : 0 ) + padB );
var cx = 12 + plotW / 2;
var cy = padT + plotH / 2;
var outerR = spec.outerRadius ? Number( spec.outerRadius ) : ( Math.min( plotW, plotH ) * 0.42 );
var innerR = spec.innerRadius ? Math.min( outerR * 0.85, Number( spec.innerRadius ) ) : 0;
var palObj = U.buildColorAndAlphaPalette( spec.colors, 'pie', items.length, 1 );
var sCmap = ( spec.seriesColorMaps && spec.seriesColorMaps[ '1' ] ) || spec.colorMap;
var hasCmap = sCmap && U.isColormapName( sCmap );
var grey = '#54595d';
var svg = U.createSvgEl( 'svg', {
viewBox: '0 0 ' + totalW + ' ' + totalH,
width: totalW,
height: totalH,
'class': 'graph-chart-svg',
style: 'display:block;max-width:100%;height:auto'
} );
var stageWrap = U.createHtmlEl(
'div',
'svg-graph gr-async-stage',
'position:relative;display:block;clear:both;width:' + totalW + 'px;max-width:100%;margin:0.5em 0'
);
var tipUI = U.createTooltipUI();
var curAngle = ( ( Number( spec.startAngle ) || 0 ) - 90 ) * DEG2RAD;
for ( var idx = 0; idx < items.length; idx++ ) {
var it = items[ idx ];
var frac = it.value / totalVal;
var nextAngle = curAngle + frac * Math.PI * 2;
var midAngle = ( curAngle + nextAngle ) / 2;
var col = hasCmap ?
U.sampleColormap( items.length > 1 ? idx / ( items.length - 1 ) : 0.5, sCmap ) :
palObj.colors[ idx % palObj.colors.length ];
it.color = col;
var dPath = buildSectorPath( cx, cy, innerR, outerR, curAngle, nextAngle );
var sliceEl = U.createSvgEl( 'path', {
d: dPath,
fill: col,
stroke: '#ffffff',
'stroke-width': 1.2,
style: 'cursor:pointer;transition:opacity 0.15s'
} );
( function ( item, midA, c ) {
sliceEl.addEventListener( 'mousemove', function () {
sliceEl.setAttribute( 'fill-opacity', '0.82' );
var tipPt = polarToCart( cx, cy, ( innerR + outerR ) * 0.55, midA );
var pctStr = ( ( item.value / totalVal ) * 100 ).toFixed( 1 ) + '%';
var valStr = U.formatNumber( item.value, spec.yAxisFormat ) + ' (' + pctStr + ')';
tipUI.show( tipPt[ 0 ], tipPt[ 1 ], totalW, totalH, c, item.label, valStr );
} );
sliceEl.addEventListener( 'mouseleave', function () {
sliceEl.removeAttribute( 'fill-opacity' );
tipUI.hide();
} );
}( it, midAngle, col ) );
svg.appendChild( sliceEl );
if ( spec.showValues && frac >= 0.045 ) {
var lblR = innerR > 0 ? ( innerR + outerR ) / 2 : outerR * 0.68;
var lPt = polarToCart( cx, cy, lblR, midAngle );
svg.appendChild( U.createSvgEl( 'text', {
x: lPt[ 0 ].toFixed( 2 ),
y: ( lPt[ 1 ] + 4 ).toFixed( 2 ),
'text-anchor': 'middle',
'font-size': '11px',
'font-weight': 'bold',
'font-family': 'sans-serif',
fill: '#ffffff',
style: 'pointer-events:none'
}, U.formatNumber( it.value, spec.yAxisFormat ) ) );
}
curAngle = nextAngle;
}
var titleText = spec.title || ( meta && meta.notice ) || '';
if ( titleText ) {
svg.appendChild( U.createSvgEl( 'text', {
x: cx.toFixed( 1 ), y: 20,
'text-anchor': 'middle', 'font-size': '13px', 'font-weight': 'bold', 'font-family': 'sans-serif', fill: grey
}, titleText ) );
}
if ( showLegend ) {
var legX = plotW + 18;
var legY = padT + 8;
var legTitle = String( spec.legend ).trim();
if ( legTitle !== '' && legTitle.toLowerCase() !== 'true' && legTitle.toLowerCase() !== 'yes' && legTitle !== '1' ) {
svg.appendChild( U.createSvgEl( 'text', {
x: legX, y: legY + 10, 'font-size': '12px', 'font-weight': 'bold', 'font-family': 'sans-serif', fill: grey
}, legTitle ) );
legY += 20;
}
for ( var li = 0; li < items.length; li++ ) {
var iy = legY + li * 18;
svg.appendChild( U.createSvgEl( 'rect', {
x: legX, y: iy, width: 12, height: 11, rx: 2, fill: items[ li ].color
} ) );
svg.appendChild( U.createSvgEl( 'text', {
x: legX + 18, y: iy + 9.5, 'font-size': '11px', 'font-family': 'sans-serif', fill: grey
}, items[ li ].label ) );
}
}
stageWrap.appendChild( svg );
stageWrap.appendChild( tipUI.el );
U.mountStage( container, stageWrap );
return true;
}
// =========================================================================
// 2. 極座標家族:radar / polarbar / rose / polarline / polararea / polarmap
// =========================================================================
function renderPolarFamily( container, spec, rows, meta ) {
var U = GW.Utils;
if ( !U || !Array.isArray( rows ) || rows.length === 0 ) {
return false;
}
var rawType = String( spec.type || 'polarbar' ).toLowerCase();
var isRadar = rawType === 'radar' || rawType === 'spider';
var isPolarLine = rawType === 'polarline' || rawType === 'polararea';
var isPolarMap = rawType === 'polarmap' || rawType === 'polarheatmap';
var isStacked = rawType.indexOf( 'stacked' ) === 0;
var xField = spec.xField || 'x';
var yField = spec.yField || 'y';
var zField = spec.zField || 'z';
var groupField = spec.groupField;
var first = rows[ 0 ] || {};
if ( first[ xField ] === undefined ) {
xField = first._xfield !== undefined ? '_xfield' : ( first.date !== undefined ? 'date' : 'x' );
}
if ( first[ yField ] === undefined ) {
yField = first._yfield !== undefined ? '_yfield' : ( first.views !== undefined ? 'views' : ( first.value !== undefined ? 'value' : 'y' ) );
}
var cats = [];
var catMap = {};
var seriesMap = {};
var seriesList = [];
var maxVal = 0;
for ( var i = 0; i < rows.length; i++ ) {
var r = rows[ i ];
var cKey = String( r[ xField ] !== undefined ? r[ xField ] : i );
var yv = Number( r[ yField ] );
if ( isNaN( yv ) ) {
continue;
}
if ( catMap[ cKey ] === undefined ) {
catMap[ cKey ] = cats.length;
cats.push( cKey );
}
var cIdx = catMap[ cKey ];
var gKey = groupField && r[ groupField ] !== undefined ? String( r[ groupField ] ) : ( spec.yAxisTitle || yField || 'Series 1' );
if ( !seriesMap[ gKey ] ) {
seriesMap[ gKey ] = { title: gKey, valuesByCat: {}, zByCat: {} };
seriesList.push( seriesMap[ gKey ] );
}
seriesMap[ gKey ].valuesByCat[ cIdx ] = yv;
if ( r[ zField ] !== undefined ) {
seriesMap[ gKey ].zByCat[ cIdx ] = Number( r[ zField ] );
}
if ( yv > maxVal ) {
maxVal = yv;
}
}
if ( cats.length === 0 || seriesList.length === 0 ) {
return false;
}
if ( isStacked && seriesList.length > 1 ) {
maxVal = 0;
for ( var ci = 0; ci < cats.length; ci++ ) {
var sum = 0;
for ( var si = 0; si < seriesList.length; si++ ) {
sum += Math.max( 0, seriesList[ si ].valuesByCat[ ci ] || 0 );
}
if ( sum > maxVal ) {
maxVal = sum;
}
}
}
var tickObj = U.computeNiceTicks( 0, maxVal > 0 ? maxVal : 10, 4 );
var rMaxVal = Math.max( 1, spec.yMax ? Number( spec.yMax ) : tickObj.max );
var plotW = Math.max( 180, Number( spec.width ) || 340 );
var plotH = Math.max( 180, Number( spec.height ) || 300 );
var showLegend = spec.legend !== undefined && spec.legend !== null && spec.legend !== false && spec.legend !== 'false';
var legW = showLegend ? 130 : 0;
var padT = ( spec.title || ( meta && meta.notice ) ) ? 34 : 18;
var padB = 22;
var totalW = plotW + legW + 30;
var totalH = padT + plotH + padB;
var cx = 15 + plotW / 2;
var cy = padT + plotH / 2;
var outerR = spec.outerRadius ? Number( spec.outerRadius ) : ( Math.min( plotW, plotH ) * 0.38 );
var innerR = ( !isRadar && spec.innerRadius ) ? Math.min( outerR * 0.7, Number( spec.innerRadius ) ) : 0;
var startRad = ( ( Number( spec.startAngle ) || 0 ) - 90 ) * DEG2RAD;
var nCats = Math.max( 1, cats.length );
var stepAngle = ( Math.PI * 2 ) / nCats;
var grey = '#54595d';
var svg = U.createSvgEl( 'svg', {
viewBox: '0 0 ' + totalW + ' ' + totalH,
width: totalW,
height: totalH,
'class': 'graph-chart-svg',
style: 'display:block;max-width:100%;height:auto'
} );
var defs = U.createSvgEl( 'defs' );
svg.appendChild( defs );
// 1. 繪製極座標同心網格與軸線
for ( var ti = 1; ti < tickObj.ticks.length; ti++ ) {
var tk = tickObj.ticks[ ti ];
if ( tk <= rMaxVal + 1e-9 ) {
var rr = innerR + ( tk / rMaxVal ) * ( outerR - innerR );
if ( isRadar && nCats >= 3 ) {
var webPts = [];
for ( var wi = 0; wi < nCats; wi++ ) {
var wp = polarToCart( cx, cy, rr, startRad + wi * stepAngle );
webPts.push( wp[ 0 ].toFixed( 2 ) + ',' + wp[ 1 ].toFixed( 2 ) );
}
svg.appendChild( U.createSvgEl( 'polygon', {
points: webPts.join( ' ' ), fill: 'none', stroke: '#d8dbdf', 'stroke-width': 1
} ) );
} else {
svg.appendChild( U.createSvgEl( 'circle', {
cx: cx.toFixed( 2 ), cy: cy.toFixed( 2 ), r: rr.toFixed( 2 ),
fill: 'none', stroke: '#d8dbdf', 'stroke-width': 1, 'stroke-dasharray': '2,2'
} ) );
}
}
}
// 繪製輻射軸與外圍類別標籤
for ( var ai = 0; ai < nCats; ai++ ) {
var ang = startRad + ( isRadar || isPolarLine ? ai * stepAngle : ( ai + 0.5 ) * stepAngle );
var spokeEnd = polarToCart( cx, cy, outerR, startRad + ai * stepAngle );
svg.appendChild( U.createSvgEl( 'line', {
x1: cx.toFixed( 2 ), y1: cy.toFixed( 2 ),
x2: spokeEnd[ 0 ].toFixed( 2 ), y2: spokeEnd[ 1 ].toFixed( 2 ),
stroke: '#e1e3e5', 'stroke-width': 1
} ) );
var lblPt = polarToCart( cx, cy, outerR + 14, ang );
var cosA = Math.cos( ang );
var anchor = Math.abs( cosA ) < 0.2 ? 'middle' : ( cosA > 0 ? 'start' : 'end' );
svg.appendChild( U.createSvgEl( 'text', {
x: lblPt[ 0 ].toFixed( 2 ), y: ( lblPt[ 1 ] + 4 ).toFixed( 2 ),
'text-anchor': anchor, 'font-size': '11px', 'font-family': 'sans-serif', fill: grey
}, cats[ ai ] ) );
}
var stageWrap = U.createHtmlEl(
'div',
'svg-graph gr-async-stage',
'position:relative;display:block;clear:both;width:' + totalW + 'px;max-width:100%;margin:0.5em 0'
);
var tipUI = U.createTooltipUI();
var palObj = U.buildColorAndAlphaPalette( spec.colors, rawType, nCats, seriesList.length );
var sCmap = ( spec.seriesColorMaps && spec.seriesColorMaps[ '1' ] ) || spec.colorMap;
var hasCmap = sCmap && U.isColormapName( sCmap );
// 2. 繪製資料圖層
if ( isRadar || isPolarLine ) {
for ( var sIdx = 0; sIdx < seriesList.length; sIdx++ ) {
var sObj = seriesList[ sIdx ];
var col = palObj.colors[ sIdx % palObj.colors.length ];
var polyPts = [];
var dotNodes = [];
for ( var cIdx2 = 0; cIdx2 < nCats; cIdx2++ ) {
var val = Math.max( 0, sObj.valuesByCat[ cIdx2 ] || 0 );
var rad = innerR + ( Math.min( rMaxVal, val ) / rMaxVal ) * ( outerR - innerR );
var ptA = startRad + cIdx2 * stepAngle;
var p = polarToCart( cx, cy, rad, ptA );
polyPts.push( p[ 0 ].toFixed( 2 ) + ',' + p[ 1 ].toFixed( 2 ) );
dotNodes.push( { x: p[ 0 ], y: p[ 1 ], val: val, cat: cats[ cIdx2 ], color: col, series: sObj.title } );
}
if ( isRadar || rawType === 'polararea' ) {
svg.appendChild( U.createSvgEl( 'polygon', {
points: polyPts.join( ' ' ), fill: col, 'fill-opacity': '0.25', stroke: col, 'stroke-width': 2
} ) );
} else {
svg.appendChild( U.createSvgEl( 'polyline', {
points: polyPts.join( ' ' ), fill: 'none', stroke: col, 'stroke-width': 2.2
} ) );
}
for ( var di = 0; di < dotNodes.length; di++ ) {
( function ( dn ) {
var cEl = U.createSvgEl( 'circle', {
cx: dn.x.toFixed( 2 ), cy: dn.y.toFixed( 2 ), r: 4,
fill: dn.color, stroke: '#ffffff', 'stroke-width': 1.2, style: 'cursor:pointer'
} );
cEl.addEventListener( 'mousemove', function () {
tipUI.show( dn.x, dn.y, totalW, totalH, dn.color, dn.series + ' (' + dn.cat + ')', U.formatNumber( dn.val, spec.yAxisFormat ) );
} );
cEl.addEventListener( 'mouseleave', function () { tipUI.hide(); } );
svg.appendChild( cEl );
}( dotNodes[ di ] ) );
}
}
} else {
// polarbar / rose / stackedpolarbar / groupedpolarbar / polarmap
var padFrac = spec.barPadding !== undefined ? Number( spec.barPadding ) : 0.12;
var usableSpan = stepAngle * ( 1 - padFrac );
var accumR = {};
for ( var ci2 = 0; ci2 < nCats; ci2++ ) {
accumR[ ci2 ] = innerR;
}
for ( var si2 = 0; si2 < seriesList.length; si2++ ) {
var sBar = seriesList[ si2 ];
var bCol = palObj.colors[ si2 % palObj.colors.length ];
for ( var ci3 = 0; ci3 < nCats; ci3++ ) {
var vBar = Math.max( 0, sBar.valuesByCat[ ci3 ] || 0 );
if ( vBar <= 0 ) {
continue;
}
var baseStartA = startRad + ci3 * stepAngle + ( stepAngle - usableSpan ) / 2;
var a0, a1, r0, r1;
if ( isStacked || isPolarMap ) {
a0 = baseStartA;
a1 = baseStartA + usableSpan;
r0 = accumR[ ci3 ];
r1 = Math.min( outerR, r0 + ( vBar / rMaxVal ) * ( outerR - innerR ) );
accumR[ ci3 ] = r1;
} else {
var subSpan = usableSpan / seriesList.length;
a0 = baseStartA + si2 * subSpan;
a1 = a0 + subSpan * 0.92;
r0 = innerR;
r1 = innerR + ( Math.min( rMaxVal, vBar ) / rMaxVal ) * ( outerR - innerR );
}
var fillCol = hasCmap ? U.sampleColormap( vBar / rMaxVal, sCmap ) : bCol;
var secPath = buildSectorPath( cx, cy, r0, r1, a0, a1 );
var secEl = U.createSvgEl( 'path', {
d: secPath, fill: fillCol, 'fill-opacity': '0.88', stroke: '#ffffff', 'stroke-width': 0.8, style: 'cursor:pointer'
} );
( function ( catLbl, sTitle, valNum, midA, midR, cHex ) {
secEl.addEventListener( 'mousemove', function () {
var tp = polarToCart( cx, cy, midR, midA );
var lText = seriesList.length > 1 ? ( sTitle + ' (' + catLbl + ')' ) : catLbl;
tipUI.show( tp[ 0 ], tp[ 1 ], totalW, totalH, cHex, lText, U.formatNumber( valNum, spec.yAxisFormat ) );
} );
secEl.addEventListener( 'mouseleave', function () { tipUI.hide(); } );
}( cats[ ci3 ], sBar.title, vBar, ( a0 + a1 ) / 2, ( r0 + r1 ) / 2, fillCol ) );
svg.appendChild( secEl );
}
}
}
// 繪製徑向刻度數值
for ( var rti = 1; rti < tickObj.ticks.length; rti++ ) {
var rtk = tickObj.ticks[ rti ];
if ( rtk <= rMaxVal + 1e-9 ) {
var rPos = innerR + ( rtk / rMaxVal ) * ( outerR - innerR );
svg.appendChild( U.createSvgEl( 'text', {
x: ( cx + 4 ).toFixed( 1 ), y: ( cy - rPos + 10 ).toFixed( 1 ),
'font-size': '10px', 'font-family': 'sans-serif', fill: grey
}, U.formatNumber( rtk, spec.yAxisFormat ) ) );
}
}
var titleText = spec.title || ( meta && meta.notice ) || '';
if ( titleText ) {
svg.appendChild( U.createSvgEl( 'text', {
x: cx.toFixed( 1 ), y: 20,
'text-anchor': 'middle', 'font-size': '13px', 'font-weight': 'bold', 'font-family': 'sans-serif', fill: grey
}, titleText ) );
}
if ( showLegend && seriesList.length > 0 ) {
var legX = plotW + 22;
var legY = padT + 8;
for ( var li = 0; li < seriesList.length; li++ ) {
var iy = legY + li * 18;
var lCol = palObj.colors[ li % palObj.colors.length ];
svg.appendChild( U.createSvgEl( 'rect', {
x: legX, y: iy, width: 12, height: 11, rx: 2, fill: lCol
} ) );
svg.appendChild( U.createSvgEl( 'text', {
x: legX + 18, y: iy + 9.5, 'font-size': '11px', 'font-family': 'sans-serif', fill: grey
}, seriesList[ li ].title ) );
}
}
stageWrap.appendChild( svg );
stageWrap.appendChild( tipUI.el );
U.mountStage( container, stageWrap );
return true;
}
// =========================================================================
// 3. 補齊 2D 矩陣熱圖 (heatmap)
// =========================================================================
function renderHeatmap2D( container, spec, rows, meta ) {
var U = GW.Utils;
if ( !U || !Array.isArray( rows ) || rows.length === 0 ) {
return false;
}
var xField = spec.xField || 'x';
var yField = spec.yField || 'y';
var zField = spec.zField || 'z';
var xCats = [], yCats = [], xMap = {}, yMap = {};
var cells = [];
var zMin = Infinity, zMax = -Infinity;
for ( var i = 0; i < rows.length; i++ ) {
var r = rows[ i ];
var xk = String( r[ xField ] !== undefined ? r[ xField ] : i );
var yk = String( r[ yField ] !== undefined ? r[ yField ] : 0 );
var zv = Number( r[ zField ] !== undefined ? r[ zField ] : r.value );
if ( isNaN( zv ) ) {
continue;
}
if ( xMap[ xk ] === undefined ) { xMap[ xk ] = xCats.length; xCats.push( xk ); }
if ( yMap[ yk ] === undefined ) { yMap[ yk ] = yCats.length; yCats.push( yk ); }
if ( zv < zMin ) { zMin = zv; }
if ( zv > zMax ) { zMax = zv; }
cells.push( { xi: xMap[ xk ], yi: yMap[ yk ], xLbl: xk, yLbl: yk, z: zv } );
}
if ( cells.length === 0 ) {
return false;
}
if ( zMax <= zMin ) { zMax = zMin + 1; }
var plotW = Math.max( 140, Number( spec.width ) || 400 );
var plotH = Math.max( 100, Number( spec.height ) || 240 );
var padL = 64, padR = 24, padT = 30, padB = 34;
var totalW = padL + plotW + padR;
var totalH = padT + plotH + padB;
var cw = plotW / Math.max( 1, xCats.length );
var ch = plotH / Math.max( 1, yCats.length );
var cmap = spec.colorMap || 'viridis';
var grey = '#54595d';
var svg = U.createSvgEl( 'svg', {
viewBox: '0 0 ' + totalW + ' ' + totalH,
width: totalW, height: totalH, 'class': 'graph-chart-svg',
style: 'display:block;max-width:100%;height:auto'
} );
var stageWrap = U.createHtmlEl(
'div', 'svg-graph gr-async-stage',
'position:relative;display:block;clear:both;width:' + totalW + 'px;max-width:100%;margin:0.5em 0'
);
var tipUI = U.createTooltipUI();
for ( var ci = 0; ci < cells.length; ci++ ) {
( function ( cell ) {
var cx = padL + cell.xi * cw;
var cy = padT + cell.yi * ch;
var col = U.sampleColormap( ( cell.z - zMin ) / ( zMax - zMin ), cmap );
var rectEl = U.createSvgEl( 'rect', {
x: cx.toFixed( 2 ), y: cy.toFixed( 2 ),
width: Math.max( 1, cw - 1 ).toFixed( 2 ), height: Math.max( 1, ch - 1 ).toFixed( 2 ),
fill: col, style: 'cursor:pointer'
} );
rectEl.addEventListener( 'mousemove', function () {
tipUI.show( cx + cw / 2, cy, totalW, totalH, col, cell.xLbl + ', ' + cell.yLbl, U.formatNumber( cell.z, spec.zAxisFormat ) );
} );
rectEl.addEventListener( 'mouseleave', function () { tipUI.hide(); } );
svg.appendChild( rectEl );
}( cells[ ci ] ) );
}
for ( var xi = 0; xi < xCats.length; xi++ ) {
svg.appendChild( U.createSvgEl( 'text', {
x: ( padL + ( xi + 0.5 ) * cw ).toFixed( 2 ), y: padT + plotH + 16,
'text-anchor': 'middle', 'font-size': '11px', 'font-family': 'sans-serif', fill: grey
}, xCats[ xi ] ) );
}
for ( var yi = 0; yi < yCats.length; yi++ ) {
svg.appendChild( U.createSvgEl( 'text', {
x: padL - 8, y: ( padT + ( yi + 0.5 ) * ch + 4 ).toFixed( 2 ),
'text-anchor': 'end', 'font-size': '11px', 'font-family': 'sans-serif', fill: grey
}, yCats[ yi ] ) );
}
var titleText = spec.title || ( meta && meta.notice ) || '';
if ( titleText ) {
svg.appendChild( U.createSvgEl( 'text', {
x: ( padL + plotW / 2 ).toFixed( 1 ), y: 20,
'text-anchor': 'middle', 'font-size': '13px', 'font-weight': 'bold', 'font-family': 'sans-serif', fill: grey
}, titleText ) );
}
stageWrap.appendChild( svg );
stageWrap.appendChild( tipUI.el );
U.mountStage( container, stageWrap );
return true;
}
GW.Renderers.pie = renderPieChart;
GW.Renderers.donut = renderPieChart;
var polarTypes = [
'radar', 'spider', 'polarline', 'polararea',
'polarbar', 'rose', 'stackedpolarbar', 'groupedpolarbar',
'polarmap', 'polarheatmap'
];
for ( var i = 0; i < polarTypes.length; i++ ) {
GW.Renderers[ polarTypes[ i ] ] = renderPolarFamily;
}
GW.Renderers.heatmap = renderHeatmap2D;
GW.ChartPolar = renderPolarFamily;
}() );