User:A2569875/Module:Graph/Chart2D.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
// Module:Graph/Chart2D.js
( function () {
'use strict';
window.mw = window.mw || {};
mw.libs = mw.libs || {};
var GW = mw.libs.GraphWidget = mw.libs.GraphWidget || {};
GW.Renderers = GW.Renderers || {};
// 將 API 平面紀錄集整理為多數列結構 (對齊 DataProcess.wrangleRecordsToArgs)
function pivotRecords( spec, rows ) {
var xField = spec.xField || 'date';
var yField = spec.yField || 'views';
var sizeField = spec.sizeField;
var groupField = spec.groupField;
var first = rows[ 0 ] || {};
if ( first[ xField ] === undefined ) {
if ( first._xfield !== undefined ) {
xField = '_xfield';
} else if ( first.date !== undefined ) {
xField = 'date';
} else if ( first.timestamp !== undefined ) {
xField = 'timestamp';
} else if ( first.x !== undefined ) {
xField = 'x';
}
}
if ( first[ yField ] === undefined ) {
if ( first._yfield !== undefined ) {
yField = '_yfield';
} else if ( first.views !== undefined ) {
yField = 'views';
} else if ( first.value !== undefined ) {
yField = 'value';
} else if ( first.y !== undefined ) {
yField = 'y';
}
}
var rawType = String( spec.type || 'line' ).toLowerCase();
var isStacked = rawType.indexOf( 'stacked' ) === 0;
var baseType = isStacked ? rawType.slice( 7 ) : rawType;
if ( baseType === 'bar' || baseType === 'groupedbar' || baseType === 'groupedrect' || baseType === 'hist' || baseType === 'histogram' ) {
baseType = 'rect';
}
var isDateX = spec.xType === 'date' || xField === 'date' || xField === 'timestamp' || xField === '_xfield';
var isOrdinalX = ( baseType === 'rect' && rawType.indexOf( 'hist' ) === -1 ) || spec.xType === 'string';
var xCats = [];
var xCatMap = {};
var seriesMap = {};
var seriesList = [];
for ( var i = 0; i < rows.length; i++ ) {
var r = rows[ i ];
var rawX = r[ xField ];
var rawY = r[ yField ];
var yv = Number( rawY );
if ( rawX === undefined || rawX === null || isNaN( yv ) ) {
continue;
}
var xv;
if ( isOrdinalX ) {
var xKey = String( rawX );
if ( xCatMap[ xKey ] === undefined ) {
xCatMap[ xKey ] = xCats.length;
xCats.push( xKey );
}
xv = xCatMap[ xKey ];
} else if ( isDateX ) {
xv = GW.Utils.parseDateMs( rawX );
if ( isNaN( xv ) ) {
isDateX = false;
isOrdinalX = true;
var sKey = String( rawX );
if ( xCatMap[ sKey ] === undefined ) {
xCatMap[ sKey ] = xCats.length;
xCats.push( sKey );
}
xv = xCatMap[ sKey ];
}
} else {
xv = Number( rawX );
if ( isNaN( xv ) ) {
var dMs = GW.Utils.parseDateMs( rawX );
if ( !isNaN( dMs ) ) {
isDateX = true;
xv = dMs;
} else {
isOrdinalX = true;
var kStr = String( rawX );
if ( xCatMap[ kStr ] === undefined ) {
xCatMap[ kStr ] = xCats.length;
xCats.push( kStr );
}
xv = xCatMap[ kStr ];
}
}
}
var gKey = ( groupField && r[ groupField ] !== undefined ) ? String( r[ groupField ] ) : ( spec.yAxisTitle || yField || 'y1' );
if ( !seriesMap[ gKey ] ) {
seriesMap[ gKey ] = { title: gKey, pts: [], byX: {} };
seriesList.push( seriesMap[ gKey ] );
}
var sv = sizeField && r[ sizeField ] !== undefined ? Number( r[ sizeField ] ) : null;
var ptObj = { x: xv, y: yv, y0: 0, y1: yv, rawX: rawX, size: sv };
seriesMap[ gKey ].pts.push( ptObj );
seriesMap[ gKey ].byX[ xv ] = ptObj;
}
if ( isStacked && seriesList.length > 1 ) {
var allXKeys = [];
var seenX = {};
for ( var s = 0; s < seriesList.length; s++ ) {
for ( var p = 0; p < seriesList[ s ].pts.length; p++ ) {
var xk = seriesList[ s ].pts[ p ].x;
if ( !seenX[ xk ] ) {
seenX[ xk ] = true;
allXKeys.push( xk );
}
}
}
allXKeys.sort( function ( a, b ) { return a - b; } );
for ( var xi = 0; xi < allXKeys.length; xi++ ) {
var curX = allXKeys[ xi ];
var posSum = 0, negSum = 0;
for ( var si = 0; si < seriesList.length; si++ ) {
var pt = seriesList[ si ].byX[ curX ];
if ( pt ) {
if ( pt.y >= 0 ) {
pt.y0 = posSum;
posSum += pt.y;
pt.y1 = posSum;
} else {
pt.y0 = negSum;
negSum += pt.y;
pt.y1 = negSum;
}
}
}
}
}
return {
baseType: baseType,
isStacked: isStacked,
isDateX: isDateX,
isOrdinalX: isOrdinalX,
xCats: xCats,
seriesList: seriesList
};
}
// 主直角座標系渲染器 (支援折線、面積、堆疊、長條、散佈與氣泡圖)
function renderCartesian2D( container, spec, rows, meta ) {
var U = GW.Utils;
if ( !U || !Array.isArray( rows ) || rows.length === 0 ) {
return false;
}
var pv = pivotRecords( spec, rows );
var seriesList = pv.seriesList;
if ( seriesList.length === 0 ) {
return false;
}
var plotW = Math.max( 100, Number( spec.width ) || 450 );
var plotH = Math.max( 70, Number( spec.height ) || 200 );
var grey = '#54595d';
var baseType = pv.baseType;
var isStacked = pv.isStacked;
var isDateX = pv.isDateX;
var isOrdinalX = pv.isOrdinalX;
var xCats = pv.xCats;
var dataXMin = Infinity, dataXMax = -Infinity;
var dataYMin = ( isStacked || baseType === 'rect' || baseType === 'area' ) ? 0 : Infinity;
var dataYMax = -Infinity;
var sizeMin = Infinity, sizeMax = -Infinity;
for ( var si = 0; si < seriesList.length; si++ ) {
var pts = seriesList[ si ].pts;
for ( var pi = 0; pi < pts.length; pi++ ) {
var pt = pts[ pi ];
if ( pt.x < dataXMin ) {
dataXMin = pt.x;
}
if ( pt.x > dataXMax ) {
dataXMax = pt.x;
}
if ( pt.y0 < dataYMin ) {
dataYMin = pt.y0;
}
if ( pt.y1 < dataYMin ) {
dataYMin = pt.y1;
}
if ( pt.y0 > dataYMax ) {
dataYMax = pt.y0;
}
if ( pt.y1 > dataYMax ) {
dataYMax = pt.y1;
}
if ( pt.size !== null && !isNaN( pt.size ) ) {
if ( pt.size < sizeMin ) {
sizeMin = pt.size;
}
if ( pt.size > sizeMax ) {
sizeMax = pt.size;
}
}
}
}
if ( dataYMin === Infinity ) {
dataYMin = 0;
dataYMax = 10;
}
if ( spec.yScaleType === 'log' && dataYMin <= 0 ) {
dataYMin = 1;
}
var reqYMin = spec.yMin !== undefined && spec.yMin !== null ? Number( spec.yMin ) : dataYMin;
var reqYMax = spec.yMax !== undefined && spec.yMax !== null ? Number( spec.yMax ) : dataYMax;
var yTickObj = U.computeNiceTicks( reqYMin, reqYMax, Math.max( 3, Math.floor( plotH / 36 ) ), spec.yScaleType, spec.yType === 'integer' );
var yMin = ( spec.yMin !== undefined && spec.yMin !== null ) ? Number( spec.yMin ) : yTickObj.min;
var yMax = ( spec.yMax !== undefined && spec.yMax !== null ) ? Number( spec.yMax ) : yTickObj.max;
if ( yMax <= yMin ) {
yMax = yMin + 1;
}
var xMin = dataXMin, xMax = dataXMax;
var xTicks = [];
if ( !isOrdinalX ) {
if ( isDateX ) {
if ( xMax <= xMin ) {
xMax = xMin + 86400000;
}
var nDateTicks = Math.max( 3, Math.min( 7, Math.floor( plotW / 85 ) ) );
for ( var dti = 0; dti <= nDateTicks; dti++ ) {
xTicks.push( xMin + ( dti / nDateTicks ) * ( xMax - xMin ) );
}
} else {
var xTickObj = U.computeNiceTicks( xMin, xMax, Math.max( 3, Math.floor( plotW / 65 ) ), spec.xScaleType, spec.xType === 'integer' );
xMin = xTickObj.min;
xMax = xTickObj.max;
xTicks = xTickObj.ticks;
}
}
var maxYLblLen = 3;
for ( var yi = 0; yi < yTickObj.ticks.length; yi++ ) {
var sLbl = U.formatNumber( yTickObj.ticks[ yi ], spec.yAxisFormat, spec.yType === 'integer' );
if ( sLbl.length > maxYLblLen ) {
maxYLblLen = sLbl.length;
}
}
var showLegend = spec.legend !== undefined && spec.legend !== null && spec.legend !== false && spec.legend !== 'false' && spec.legend !== '0';
var legW = 0;
if ( showLegend && seriesList.length > 0 ) {
legW = 80;
for ( var li = 0; li < seriesList.length; li++ ) {
legW = Math.max( legW, seriesList[ li ].title.length * 7.5 + 34 );
}
legW = Math.min( 220, legW );
}
var xAngle = Number( spec.xAxisAngle ) || 0;
var padL = spec.hideAxes ? 12 : ( Math.max( 38, maxYLblLen * 7 + 14 ) + ( spec.yAxisTitle ? 18 : 0 ) );
var padR = 16 + legW;
var padT = ( spec.title || ( meta && meta.notice ) ) ? 30 : 14;
var padB = spec.hideAxes ? 12 : ( 26 + ( Math.abs( xAngle ) > 15 ? 22 : 0 ) + ( spec.xAxisTitle ? 18 : 0 ) );
var totalW = padL + plotW + padR;
var totalH = Math.max( padT + plotH + padB, padT + ( showLegend ? seriesList.length * 18 + 28 : 0 ) + padB );
var nCats = Math.max( 1, xCats.length );
var mapX = function ( val ) {
if ( isOrdinalX ) {
if ( baseType === 'rect' ) {
return padL + ( ( val + 0.5 ) / nCats ) * plotW;
}
return nCats <= 1 ? ( padL + plotW / 2 ) : ( padL + ( val / ( nCats - 1 ) ) * plotW );
}
return padL + U.applyScale( val, xMin, xMax, spec.xScaleType ) * plotW;
};
var mapY = function ( val ) {
return padT + plotH - U.applyScale( val, yMin, yMax, spec.yScaleType ) * plotH;
};
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' );
var clipPath = U.createSvgEl( 'clipPath', { id: 'gr-async-clip' } );
clipPath.appendChild( U.createSvgEl( 'rect', { x: padL, y: padT, width: plotW, height: plotH } ) );
defs.appendChild( clipPath );
svg.appendChild( defs );
// 繪製座標軸與格線
if ( !spec.hideAxes ) {
for ( var yti = 0; yti < yTickObj.ticks.length; yti++ ) {
var ytk = yTickObj.ticks[ yti ];
if ( ytk >= yMin - 1e-9 && ytk <= yMax + 1e-9 ) {
var py = mapY( ytk );
if ( spec.yGrid !== false ) {
svg.appendChild( U.createSvgEl( 'line', {
x1: padL, y1: py.toFixed( 2 ), x2: padL + plotW, y2: py.toFixed( 2 ),
stroke: '#e1e3e5', 'stroke-width': 1
} ) );
}
svg.appendChild( U.createSvgEl( 'line', {
x1: padL - 5, y1: py.toFixed( 2 ), x2: padL, y2: py.toFixed( 2 ),
stroke: grey, 'stroke-width': 1
} ) );
svg.appendChild( U.createSvgEl( 'text', {
x: padL - 8, y: ( py + 3.8 ).toFixed( 2 ),
'text-anchor': 'end', 'font-size': '11px', 'font-family': 'sans-serif', fill: grey
}, U.formatNumber( ytk, spec.yAxisFormat, spec.yType === 'integer' ) ) );
}
}
if ( isOrdinalX ) {
var stepCat = Math.max( 1, Math.ceil( nCats / Math.max( 2, Math.floor( plotW / ( Math.abs( xAngle ) > 20 ? 20 : 45 ) ) ) ) );
for ( var ci = 0; ci < nCats; ci += stepCat ) {
var cpx = mapX( ci );
svg.appendChild( U.createSvgEl( 'line', {
x1: cpx.toFixed( 2 ), y1: padT + plotH, x2: cpx.toFixed( 2 ), y2: padT + plotH + 5,
stroke: grey, 'stroke-width': 1
} ) );
var txtAttrs = {
x: cpx.toFixed( 2 ), y: padT + plotH + 15,
'text-anchor': xAngle < 0 ? 'end' : ( xAngle > 0 ? 'start' : 'middle' ),
'font-size': '11px', 'font-family': 'sans-serif', fill: grey
};
if ( xAngle !== 0 ) {
txtAttrs.transform = 'rotate(' + xAngle + ', ' + cpx.toFixed( 2 ) + ', ' + ( padT + plotH + 12 ) + ')';
}
svg.appendChild( U.createSvgEl( 'text', txtAttrs, xCats[ ci ] ) );
}
} else {
for ( var xti = 0; xti < xTicks.length; xti++ ) {
var xtk = xTicks[ xti ];
var px = mapX( xtk );
if ( spec.xGrid ) {
svg.appendChild( U.createSvgEl( 'line', {
x1: px.toFixed( 2 ), y1: padT, x2: px.toFixed( 2 ), y2: padT + plotH,
stroke: '#e1e3e5', 'stroke-width': 1
} ) );
}
svg.appendChild( U.createSvgEl( 'line', {
x1: px.toFixed( 2 ), y1: padT + plotH, x2: px.toFixed( 2 ), y2: padT + plotH + 5,
stroke: grey, 'stroke-width': 1
} ) );
var xLbl = isDateX ? U.formatDateMs( xtk, spec.xAxisFormat, xMax - xMin ) : U.formatNumber( xtk, spec.xAxisFormat, spec.xType === 'integer' );
svg.appendChild( U.createSvgEl( 'text', {
x: px.toFixed( 2 ), y: padT + plotH + 16,
'text-anchor': 'middle', 'font-size': '11px', 'font-family': 'sans-serif', fill: grey
}, xLbl ) );
}
}
}
// 資料繪製層
var plotGroup = U.createSvgEl( 'g', { 'clip-path': 'url(#gr-async-clip)' } );
var palObj = U.buildColorAndAlphaPalette( spec.colors, baseType, nCats, seriesList.length );
var baseY0 = mapY( Math.max( yMin, Math.min( yMax, 0 ) ) );
var cMin = spec.domainMin !== undefined && spec.domainMin !== null ? Number( spec.domainMin ) : yMin;
var cMax = spec.domainMax !== undefined && spec.domainMax !== null ? Number( spec.domainMax ) : yMax;
if ( cMax <= cMin ) {
cMax = cMin + 1;
}
var drawPageviewsArea = ( baseType === 'area' ) || ( spec.rawUrl && spec.rawUrl.indexOf( 'pageviews' ) !== -1 );
var allInteractivePts = [];
for ( var sIdx = 0; sIdx < seriesList.length; sIdx++ ) {
var sObj = seriesList[ sIdx ];
var c = palObj.colors[ sIdx % palObj.colors.length ];
var alpha = palObj.alphas[ sIdx % palObj.alphas.length ] || 1;
var sCmap = ( spec.seriesColorMaps && spec.seriesColorMaps[ String( sIdx + 1 ) ] ) || spec.colorMap;
var hasCmap = sCmap && U.isColormapName( sCmap );
var baseCol = ( sCmap && !hasCmap && U.isValidColorToken( sCmap ) ) ? sCmap : c;
var lw = spec.linewidth !== undefined && spec.linewidth !== null ? Number( spec.linewidth ) : ( baseType === 'area' ? 1.8 : 2.5 );
if ( baseType === 'rect' ) {
var padRatio = spec.barPadding !== undefined && spec.barPadding !== null ? Number( spec.barPadding ) : 0.18;
var bandW = ( plotW / Math.max( 1, isOrdinalX ? nCats : sObj.pts.length ) ) * ( 1 - padRatio );
var gap = ( !isStacked && seriesList.length > 1 ) ? 1.5 : 0;
var subBarW = isStacked ? bandW : Math.max( 1.5, ( bandW - ( seriesList.length - 1 ) * gap ) / seriesList.length );
for ( var bi = 0; bi < sObj.pts.length; bi++ ) {
var bPt = sObj.pts[ bi ];
var centerPx = mapX( bPt.x );
var barX = isStacked ? ( centerPx - bandW / 2 ) : ( centerPx - bandW / 2 + sIdx * ( subBarW + gap ) );
var py1 = mapY( bPt.y1 );
var py0 = isStacked ? mapY( bPt.y0 ) : baseY0;
var topY = Math.min( py1, py0 );
var rectH = Math.max( 1, Math.abs( py0 - py1 ) );
var barCol = hasCmap ? U.sampleColormap( ( bPt.y - cMin ) / ( cMax - cMin ), sCmap ) : baseCol;
plotGroup.appendChild( U.createSvgEl( 'rect', {
x: barX.toFixed( 2 ), y: topY.toFixed( 2 ),
width: subBarW.toFixed( 2 ), height: rectH.toFixed( 2 ),
fill: barCol, 'fill-opacity': alpha < 1 ? alpha.toFixed( 2 ) : '0.9'
} ) );
bPt.sx = barX + subBarW / 2;
bPt.sy = topY;
bPt.color = barCol;
bPt.seriesTitle = sObj.title;
allInteractivePts.push( bPt );
}
} else {
var pathCol = baseCol;
if ( hasCmap ) {
var gradId = 'gr-async-cmap-' + sIdx + '-' + Math.floor( Math.random() * 10000 );
defs.appendChild( U.buildColormapGradientEl( gradId, sCmap, 0, mapY( cMin ), 0, mapY( cMax ), true ) );
pathCol = 'url(#' + gradId + ')';
}
var topCoords = [];
var botCoords = [];
for ( var pi2 = 0; pi2 < sObj.pts.length; pi2++ ) {
var pt2 = sObj.pts[ pi2 ];
var sx = mapX( pt2.x );
var sy1 = mapY( pt2.y1 );
var sy0 = mapY( pt2.y0 );
var ptCol = hasCmap ? U.sampleColormap( ( pt2.y - cMin ) / ( cMax - cMin ), sCmap ) : baseCol;
pt2.sx = sx;
pt2.sy = sy1;
pt2.color = ptCol;
pt2.seriesTitle = sObj.title;
topCoords.push( [ sx, sy1 ] );
botCoords.push( [ sx, sy0 ] );
allInteractivePts.push( pt2 );
if ( spec.showSymbols || lw === 0 || pt2.size !== null ) {
var symR = 3.8;
if ( pt2.size !== null && !isNaN( pt2.size ) ) {
symR = sizeMax > sizeMin ? ( 4 + Math.sqrt( ( pt2.size - sizeMin ) / ( sizeMax - sizeMin ) ) * 12 ) : 6;
}
plotGroup.appendChild( U.createSymbolEl(
spec.symbolsShape || 'circle',
sx, sy1, symR,
spec.symbolsNoFill ? 'none' : ptCol,
ptCol,
spec.symbolsStroke || ( spec.symbolsNoFill ? 1.8 : 0 ),
pt2.size !== null ? 0.78 : alpha
) );
}
}
if ( topCoords.length >= 2 ) {
var topD = U.buildPathD( topCoords, spec.interpolate || 'monotone', false );
if ( drawPageviewsArea ) {
var areaD;
if ( isStacked ) {
areaD = topD + ' ' + U.buildPathD( botCoords, spec.interpolate || 'monotone', true ) + ' Z';
} else {
var firstX = topCoords[ 0 ][ 0 ].toFixed( 2 );
var lastX = topCoords[ topCoords.length - 1 ][ 0 ].toFixed( 2 );
areaD = topD + ' L ' + lastX + ',' + baseY0.toFixed( 2 ) + ' L ' + firstX + ',' + baseY0.toFixed( 2 ) + ' Z';
}
plotGroup.appendChild( U.createSvgEl( 'path', {
d: areaD, fill: pathCol, 'fill-opacity': isStacked ? '0.8' : '0.30', stroke: 'none'
} ) );
}
if ( lw > 0 ) {
plotGroup.appendChild( U.createSvgEl( 'path', {
d: topD, fill: 'none', stroke: pathCol, 'stroke-width': lw,
'stroke-linecap': 'round', 'stroke-linejoin': 'round'
} ) );
}
}
}
}
svg.appendChild( plotGroup );
if ( !spec.hideAxes ) {
svg.appendChild( U.createSvgEl( 'polyline', {
points: padL + ',' + padT + ' ' + padL + ',' + ( padT + plotH ) + ' ' + ( padL + plotW ) + ',' + ( padT + plotH ),
fill: 'none', stroke: grey, 'stroke-width': 1.8
} ) );
}
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 ) );
}
if ( spec.xAxisTitle && !spec.hideAxes ) {
svg.appendChild( U.createSvgEl( 'text', {
x: ( padL + plotW / 2 ).toFixed( 1 ), y: totalH - 6,
'text-anchor': 'middle', 'font-size': '12px', 'font-weight': 'bold', 'font-family': 'sans-serif', fill: grey
}, spec.xAxisTitle ) );
}
if ( spec.yAxisTitle && !spec.hideAxes ) {
var yc = padT + plotH / 2;
svg.appendChild( U.createSvgEl( 'text', {
x: 14, y: yc.toFixed( 1 ),
'text-anchor': 'middle', 'font-size': '12px', 'font-weight': 'bold', 'font-family': 'sans-serif',
fill: grey, transform: 'rotate(-90, 14, ' + yc.toFixed( 1 ) + ')'
}, spec.yAxisTitle ) );
}
if ( showLegend && seriesList.length > 0 ) {
var legX = padL + plotW + 14;
var legY = padT + 4;
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 lIdx = 0; lIdx < seriesList.length; lIdx++ ) {
var iy = legY + lIdx * 18;
var lCol = palObj.colors[ lIdx % palObj.colors.length ];
if ( baseType === 'line' ) {
svg.appendChild( U.createSvgEl( 'line', {
x1: legX, y1: iy + 5.5, x2: legX + 14, y2: iy + 5.5, stroke: lCol, 'stroke-width': 2.5
} ) );
} else {
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[ lIdx ].title ) );
}
}
// 互動十字準線與 Tooltip
var guideLine = U.createSvgEl( 'line', {
x1: padL, y1: padT, x2: padL, y2: padT + plotH,
stroke: '#888888', 'stroke-width': 1, 'stroke-dasharray': '3,3',
style: 'display:none;pointer-events:none'
} );
var focusDot = U.createSvgEl( 'circle', {
cx: padL, cy: padT + plotH, r: 4.5,
fill: palObj.colors[ 0 ], stroke: '#ffffff', 'stroke-width': 1.8,
style: 'display:none;pointer-events:none'
} );
svg.appendChild( guideLine );
svg.appendChild( focusDot );
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();
svg.addEventListener( 'mousemove', function ( ev ) {
var rect = svg.getBoundingClientRect();
var scaleX = totalW / Math.max( 1, rect.width );
var scaleY = totalH / Math.max( 1, rect.height );
var mx = ( ev.clientX - rect.left ) * scaleX;
var my = ( ev.clientY - rect.top ) * scaleY;
if ( mx < padL - 4 || mx > padL + plotW + 4 ) {
guideLine.style.display = 'none';
focusDot.style.display = 'none';
tipUI.hide();
return;
}
var bestPt = null;
var bestScore = Infinity;
for ( var i = 0; i < allInteractivePts.length; i++ ) {
var pt = allInteractivePts[ i ];
var dx = Math.abs( pt.sx - mx );
var dy = Math.abs( pt.sy - my );
var score = seriesList.length > 1 ? ( dx * 2 + dy * 0.5 ) : dx;
if ( score < bestScore ) {
bestScore = score;
bestPt = pt;
}
}
if ( bestPt ) {
guideLine.setAttribute( 'x1', bestPt.sx.toFixed( 2 ) );
guideLine.setAttribute( 'x2', bestPt.sx.toFixed( 2 ) );
guideLine.style.display = '';
focusDot.setAttribute( 'cx', bestPt.sx.toFixed( 2 ) );
focusDot.setAttribute( 'cy', bestPt.sy.toFixed( 2 ) );
focusDot.setAttribute( 'fill', bestPt.color );
focusDot.style.display = '';
var xStr = isOrdinalX ? ( xCats[ bestPt.x ] || String( bestPt.rawX ) ) :
( isDateX ? U.formatDateMs( bestPt.x, spec.xAxisFormat, 0 ) : U.formatNumber( bestPt.x, spec.xAxisFormat, spec.xType === 'integer' ) );
var yStr = U.formatNumber( bestPt.y, spec.yAxisFormat, spec.yType === 'integer' );
var lbl = seriesList.length > 1 ? ( bestPt.seriesTitle + ' (' + xStr + ')' ) : xStr;
tipUI.show( bestPt.sx, bestPt.sy, totalW, totalH, bestPt.color, lbl, yStr );
}
} );
svg.addEventListener( 'mouseleave', function () {
guideLine.style.display = 'none';
focusDot.style.display = 'none';
tipUI.hide();
} );
stageWrap.appendChild( svg );
stageWrap.appendChild( tipUI.el );
U.mountStage( container, stageWrap );
return true;
}
var types2D = [
'line', 'area', 'stackedline', 'stackedarea',
'rect', 'bar', 'groupedbar', 'groupedrect', 'stackedrect', 'stackedbar',
'hist', 'histogram', 'groupedhist', 'scatter', 'bubble'
];
for ( var i = 0; i < types2D.length; i++ ) {
GW.Renderers[ types2D[ i ] ] = renderCartesian2D;
}
GW.Chart2D = renderCartesian2D;
}() );