Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Report Chart Generation to a Separate Script #7022

Merged
merged 8 commits into from Nov 21, 2018
27 changes: 27 additions & 0 deletions assets/js/admin/reports/charts/index.js
@@ -0,0 +1,27 @@
/* global eddAdminReportsCharts */

/**
* Internal dependencies.
*/
import { render as lineChartRender } from './line.js';
import { render as pieChartRender } from './pie.js';
import { isPieChart } from './utils.js';

// Set ChartJS defaults.
Chart.defaults.global.pointHitDetectionRadius = 5;

// Get Bootstrapped chart data.
const { charts } = eddAdminReportsCharts;

/**
* Render the registered charts.
*/
_.each( charts, ( config ) => {
const isPie = isPieChart( config );

if ( isPieChart( config ) ) {
pieChartRender( config );
} else {
lineChartRender( config );
}
} );
73 changes: 73 additions & 0 deletions assets/js/admin/reports/charts/line.js
@@ -0,0 +1,73 @@
/* global Chart */

/**
* Internal dependencies.
*/
import { getLabelWithTypeCondition, toolTipBaseConfig } from './utils';

/**
* Render a line chart.
*
* @param {Object} config Global chart config.
* @return {Chart}
*/
export const render = ( config ) => {
const {
dates,
options,
data,
target,
} = config;

// Convert dataset x-axis values to moment() objects.
_.each( data.datasets, ( dataset ) => {
_.each( dataset.data, ( pair, index ) => {
if ( ! dates.hour_by_hour ) {
pair.x = moment( pair.x ).utcOffset( 0 ).format( 'LLL' );
} else {
pair.x = moment( pair.x ).utcOffset( dates.utc_offset ).format( 'LLL' );
}
} );
} );

// Set min and max moment() values for the x-axis.
// @todo Not sure this is the correct way to be setting this?
_.each( options.scales.xAxes, ( xaxis ) => {
if ( ! dates.day_by_day ) {
xaxis.time.unit = 'month';
}

xaxis.time.min = moment( dates.start.date );
xaxis.time.max = moment( dates.end.date );
} );

// Config tooltips.
config.options.tooltips = tooltipConfig( config );

// Render
return new Chart( document.getElementById( target ), config );
};

/**
* Get custom tooltip config for line charts.
*
* @param {Object} config Global chart config.
* @return {Object}
*/
export const tooltipConfig = ( config ) => ( {
...toolTipBaseConfig,

callbacks: {
/**
* Generate a label.
*
* @param {Object} t
* @param {Object} d
*/
label: function( t, d ) {
const label = getLabelWithTypeCondition( t.yLabel, config );

return `${ d.datasets[ t.datasetIndex ].label }: ${ label }`;
},
},
} );
53 changes: 53 additions & 0 deletions assets/js/admin/reports/charts/pie.js
@@ -0,0 +1,53 @@
/* global Chart */

/**
* Internal dependencies.
*/
import { toolTipBaseConfig } from './utils';

/**
* Render a line chart.
*
* @param {Object} config Global chart config.
* @return {Chart}
*/
export const render = ( config ) => {
const { target } = config;

// Config tooltips.
config.options.tooltips = tooltipConfig( config );

// Render
return new Chart( document.getElementById( target ), config );
};

/**
* Get custom tooltip config for pie charts.
*
* @param {Object} config Global chart config.
* @return {Object}
*/
export const tooltipConfig = ( config ) => ( {
...toolTipBaseConfig,

callbacks: {
/**
* Generate a label.
*
* @param {Object} t
* @param {Object} d
*/
label: function( t, d ) {
const dataset = d.datasets[ t.datasetIndex ];

const total = dataset.data.reduce( function( previousValue, currentValue, currentIndex, array ) {
return previousValue + currentValue;
} );

const currentValue = dataset.data[ t.index ];
const precentage = Math.floor( ( ( currentValue / total ) * 100 ) + 0.5 );

return `${ d.labels[ t.index ] }: ${ currentValue } (${ precentage }%)`;
},
},
} );
160 changes: 160 additions & 0 deletions assets/js/admin/reports/charts/utils.js
@@ -0,0 +1,160 @@
/* global edd_vars */

/**
* Determine if a pie graph.
*
* @todo maybe pass from data?
*
* @param {Object} config Global chart config.
* @return {Bool}
*/
export const isPieChart = ( config ) => {
const { type } = config;

return type === 'pie' || type === 'doughnut';
};

/**
* Determine if a chart's dataset has a special conditional type.
*
* Currently just checks for currency.
*
* @param {string} label Current label.
* @param {Object} config Global chart config.
*/
export const getLabelWithTypeCondition = ( label, config ) => {
const { currency_sign, currency_pos } = edd_vars;
let conditional = '';
let newLabel = label;

const {
target,
options: {
datasets,
},
} = config;

if ( datasets ) {
_.each( datasets, ( dataset ) => {
const { type } = dataset;

if ( 'currency' === type ) {
conditional += `t.datasetIndex === ${ target } || `;
}
} );
}

conditional.slice( 0, -4 );

if ( '' !== conditional ) {
// @todo support better currency locales.
const amount = label.toFixed( 2 );

if ( 'before' === currency_pos ) {
newLabel = currency_sign + amount;
} else {
newLabel = amount + currency_sign;
}
}

return newLabel;
};

/**
* Shared tooltip configuration.
*/
export const toolTipBaseConfig = {
enabled: false,
mode: 'index',
position: 'nearest',

/**
* Output a a custom tooltip.
*
* @param {Object} tooltip Tooltip data.
*/
custom: function( tooltip ) {
// Tooltip element.
let tooltipEl = document.getElementById( 'edd-chartjs-tooltip' );

if ( ! tooltipEl ) {
tooltipEl = document.createElement( 'div' );
tooltipEl.id = 'edd-chartjs-tooltip';
tooltipEl.innerHTML = '<table></table>';

this._chart.canvas.parentNode.appendChild( tooltipEl );
}

// Hide if no tooltip.
if ( tooltip.opacity === 0 ) {
tooltipEl.style.opacity = 0;
return;
}

// Set caret position.
tooltipEl.classList.remove( 'above', 'below', 'no-transform' );

if ( tooltip.yAlign ) {
tooltipEl.classList.add( tooltip.yAlign );
} else {
tooltipEl.classList.add( 'no-transform' );
}

function getBody( bodyItem ) {
return bodyItem.lines;
}

// Set Text
if ( tooltip.body ) {
const titleLines = tooltip.title || [];
const bodyLines = tooltip.body.map( getBody );

let innerHtml = '<thead>';

titleLines.forEach( function( title ) {
innerHtml += '<tr><th>' + title + '</th></tr>';
} );

innerHtml += '</thead><tbody>';

bodyLines.forEach( function( body, i ) {
const colors = tooltip.labelColors[ i ];
const { borderColor, backgroundColor } = colors;

// Super dirty check to use the legend's color.
let fill = borderColor;

if ( fill === 'rgb(230, 230, 230)' || fill === '#fff' ) {
fill = backgroundColor;
}

const style = [
`background: ${ fill }`,
`border-color: ${ fill }`,
'border-width: 2px',
];

const span = '<span class="edd-chartjs-tooltip-key" style="' + style.join( ';' ) + '"></span>';

innerHtml += '<tr><td>' + span + body + '</td></tr>';
} );

innerHtml += '</tbody>';

const tableRoot = tooltipEl.querySelector( 'table' );
tableRoot.innerHTML = innerHtml;
}

const positionY = this._chart.canvas.offsetTop;
const positionX = this._chart.canvas.offsetLeft;

// Display, position, and set styles for font
tooltipEl.style.opacity = 1;
tooltipEl.style.left = positionX + tooltip.caretX + 'px';
tooltipEl.style.top = positionY + tooltip.caretY + 'px';
tooltipEl.style.fontFamily = tooltip._bodyFontFamily;
tooltipEl.style.fontSize = tooltip.bodyFontSize + 'px';
tooltipEl.style.fontStyle = tooltip._bodyFontStyle;
tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px';
},
};
1 change: 1 addition & 0 deletions assets/js/admin/reports/index.js
Expand Up @@ -4,6 +4,7 @@
* Internal dependencies.
*/
import { eddLabelFormatter, eddLegendFormatterSales, eddLegendFormatterEarnings } from './formatting.js';
import './charts';

// Enable reports meta box toggle states.
if ( typeof postboxes !== 'undefined' && /edd-reports/.test( pagenow ) ) {
Expand Down