123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- /**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or http://ckeditor.com/license
- */
- /**
- * @fileOverview Charts for CKEditor using Chart.js.
- */
- /* global alert:false, Chart:false */
- 'use strict';
- // TODO IE8 fallback to a table maybe?
- // TODO a11y http://www.w3.org/html/wg/wiki/Correct_Hidden_Attribute_Section_v4
- ( function() {
- CKEDITOR.plugins.add( 'chart', {
- // Required plugins
- requires: 'widget,dialog',
- // Name of the file in the "icons" folder
- icons: 'chart',
- // Supported languages
- lang: 'en,pl',
- // Load library that renders charts inside CKEditor, if Chart object is not already available.
- afterInit: function() {
- var plugin = this;
- if ( typeof Chart === 'undefined' ) {
- // Chart library is loaded asynchronously, so we can draw anything only once it's loaded.
- CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( plugin.path + 'lib/chart.min.js' ), function() {
- plugin.drawCharts();
- } );
- }
- },
- // Function called on initialization of every editor instance created in the page.
- init: function( editor ) {
- var plugin = this;
- var chartDefaultHeight = editor.config.chart_height || 300;
- // Default hardcoded values used if config.chart_colors is not provided.
- var colors = editor.config.chart_colors ||
- {
- // Colors for Bar/Line chart: http://www.chartjs.org/docs/#bar-chart-data-structure
- fillColor: 'rgba(151,187,205,0.5)',
- strokeColor: 'rgba(151,187,205,0.8)',
- highlightFill: 'rgba(151,187,205,0.75)',
- highlightStroke: 'rgba(151,187,205,1)',
- // Colors for Doughnut/Pie/PolarArea charts: http://www.chartjs.org/docs/#doughnut-pie-chart-data-structure
- data: [ '#B33131', '#B66F2D', '#00b5b0', '#71B232', '#33B22D', '#31B272', '#2DB5B5', '#3172B6', '#3232B6', '#6E31B2', '#B434AF', '#B53071' ]
- };
- var config = {
- Bar: editor.config.chart_configBar || { animation: false },
- Doughnut: editor.config.chart_configDoughnut || { animateRotate: false },
- Line: editor.config.chart_configLine || { animation: false },
- Pie: editor.config.chart_configPie || { animateRotate: false },
- PolarArea: editor.config.chart_configPolarArea || { animateRotate: false }
- };
- // The number of rows in Edit Chart dialog window.
- var inputRows = editor.config.chart_maxItems || 8;
- // Inject required CSS stylesheet to classic editors because the <iframe> needs it.
- // Inline editors will ignore this, the developer is supposed to load chart.css directly on a page.
- // "this.path" is a path to the current plugin.
- editor.addContentsCss( CKEDITOR.getUrl( plugin.path + 'chart.css' ) );
- // A little bit of magic to support "Preview" feature in CKEditor (in a popup).
- // In order to transform downcasted widgets into nice charts we need to:
- // 1. Pass color settings and charts configuration through JSON.
- // 2. Load the Chart.js library
- // 3. Load a helper script that will "upcast" widgets and initiate charts.
- editor.on( 'contentPreview', function( evt ) {
- evt.data.dataValue = evt.data.dataValue.replace( /<\/head>/,
- '<script>var chartjs_colors_json = "' + JSON.stringify( colors ).replace( /\"/g, '\\"' ) + '";<\/script>' +
- '<script>var chartjs_config_json = "' + JSON.stringify( config ).replace( /\"/g, '\\"' ) + '";<\/script>' +
- '<script src="' + CKEDITOR.getUrl( plugin.path + 'lib/chart.min.js' ) + '"><\/script>' +
- '<script src="' + CKEDITOR.getUrl( plugin.path + 'widget2chart.js' ) + '"><\/script><\/head>' );
- } );
- // The dialog window to insert / edit a chart.
- CKEDITOR.dialog.add( 'chart', function( editor ) {
- var dialog = {
- title: editor.lang.chart.dialogTitle,
- minWidth: 200,
- minHeight: 100,
- // Executed every time a dialog is shown.
- onShow: function() {
- var widget = editor.widgets.focused;
- if ( !widget )
- return;
- // We edit an existing widget, so we have already some data and should set input values accordingly.
- // The dialog consists of multiple rows with two input elements each.
- // We could use "setup" callbacks for each UI element, but the we'd end up with lots of data properties.
- // So instead we merge all the values into a single object, ending with an array like:
- // [ {"value":45,"label":"Yes"}, {}, .... ]
- // to make it easier to pass it to Chart.js later.
- for ( var j = 0; j < inputRows; j++ ) {
- if ( widget.data.values[j] ) {
- // toString() is used here to set correctly zero values.
- this.setValueOf( 'data', 'value' + j, widget.data.values[j].value.toString() );
- this.setValueOf( 'data', 'label' + j, widget.data.values[j].label );
- }
- }
- },
- // Executed every time a dialog is closed (OK is pressed).
- onOk: function() {
- // ATTENTION: this.widget is not available here in CKEditor by default.
- // We added this in the "init" function of a widget ("Pass the reference to this widget to the dialog."),
- var widget = this.widget,
- values = [], value;
- // We could use "commit" callbacks in every input element to set widget data.
- // But we decided to keep multiple values in a single object (see comment in "onShow" for more details).
- for ( var j = 0; j < inputRows; j++ ) {
- value = this.getValueOf( 'data', 'value' + j );
- if ( value )
- values.push( { value: parseFloat( this.getValueOf( 'data', 'value' + j ) ), label: this.getValueOf( 'data', 'label' + j ) } );
- }
- widget.setData( 'values', values );
- widget.setData( 'chart', this.getValueOf( 'data', 'chart' ) );
- widget.setData( 'height', this.getValueOf( 'data', 'height' ) );
- },
- // Define elements in a dialog window.
- contents: [
- {
- id: 'data',
- elements: [
- {
- type: 'hbox',
- children:
- [
- {
- id: 'chart',
- type: 'select',
- label: editor.lang.chart.chartType,
- labelLayout: 'horizontal',
- // Align vertically, otherwise labels are a bit misplaced.
- labelStyle: 'display:block;padding: 4px 6px;',
- items: [
- [ editor.lang.chart.bar, 'bar' ],
- [ editor.lang.chart.line, 'line' ],
- [ editor.lang.chart.pie, 'pie' ],
- [ editor.lang.chart.polar, 'polar' ],
- [ editor.lang.chart.doughnut, 'doughnut' ]
- ],
- style: 'margin-bottom:10px',
- setup: function( widget ) {
- // Set radios to the correct value based on the widget type.
- this.setValue( widget.data.chart );
- }
- },
- {
- id: 'height',
- type: 'text',
- label: editor.lang.chart.height,
- labelLayout: 'horizontal',
- // Align vertically, otherwise labels are a bit misplaced.
- labelStyle: 'display:block;padding: 4px 6px;',
- width: '50px',
- setup: function( widget ) {
- this.setValue( widget.data.height );
- },
- validate: function() {
- var value = this.getValue(),
- pass = ( !value || !!( CKEDITOR.dialog.validate.number( value ) && value >= 0 ) );
- if ( !pass ) {
- alert( editor.lang.common.validateNumberFailed );
- this.select();
- }
- return pass;
- }
- }
- ]
- }
- ]
- }
- ]
- };
- // Rarely elements in dialog definitions are generated in loops.
- // Here we decided to make the number of "data" rows configurable, so a loop is handy.
- for ( var i = 0; i < inputRows; i++ ) {
- dialog.contents[0].elements.push( {
- type: 'hbox',
- children:
- [
- {
- id: 'value' + i,
- type: 'text',
- labelLayout: 'horizontal',
- label: editor.lang.chart.value,
- // Align vertically, otherwise labels are a bit misplaced.
- labelStyle: 'display:block;padding: 4px 6px;',
- width: '50px',
- validate: function() {
- var value = this.getValue(),
- pass = ( !value || !!( CKEDITOR.dialog.validate.number( value ) && value >= 0 ) );
- if ( !pass ) {
- alert( editor.lang.common.validateNumberFailed );
- this.select();
- }
- return pass;
- }
- },
- {
- id: 'label' + i,
- type: 'text',
- label: editor.lang.chart.label,
- labelLayout: 'horizontal',
- // Align vertically, otherwise labels are a bit misplaced.
- labelStyle: 'display:block;padding: 4px 6px;',
- width: '200px'
- }
- ]
- } );
- }
- return dialog;
- } );
- // Helper function that we'd like to run in case Chart.js library was loaded asynchronously.
- this.drawCharts = function() {
- // All available widgets are stored in an object, not an array.
- for ( var id in editor.widgets.instances ) {
- // The name was provided in editor.widgets.add()
- if ( editor.widgets.instances[id].name == 'chart' ) {
- // Our "data" callback draws widgets, so let's call it.
- editor.widgets.instances[id].fire( 'data' );
- }
- }
- };
- function renderChart( canvas, data, legend ) {
- var values = data.values,
- chartType = data.chart;
- // The code below is the same as in widget2chart.js.
- // ########## RENDER CHART START ##########
- // Prepare canvas and chart instance.
- var i, ctx = canvas.getContext( '2d' ),
- chart = new Chart( ctx ); // jshint ignore:line
- // Set some extra required colors by Pie/Doughnut charts.
- // Ugly charts will be drawn if colors are not provided for each data.
- // http://www.chartjs.org/docs/#doughnut-pie-chart-data-structure
- if ( chartType != 'bar' ) {
- for ( i = 0; i < values.length; i++ ) {
- values[i].color = colors.data[i];
- values[i].highlight = colors.data[i];
- }
- }
- // Prepare data for bar/line charts.
- if ( chartType == 'bar' || chartType == 'line' ) {
- var data = {
- // Chart.js supports multiple datasets.
- // http://www.chartjs.org/docs/#bar-chart-data-structure
- // This plugin is simple, so it supports just one.
- // Need more features? Create a Pull Request :-)
- datasets: [
- {
- label: '',
- fillColor: colors.fillColor,
- strokeColor: colors.strokeColor,
- highlightFill: colors.highlightFill,
- highlightStroke: colors.highlightStroke,
- data: []
- } ],
- labels: []
- };
- // Bar charts accept different data format than Pie/Doughnut.
- // We need to pass values inside datasets[0].data.
- for ( i = 0; i < values.length; i++ ) {
- if ( values[i].value ) {
- data.labels.push( values[i].label );
- data.datasets[0].data.push( values[i].value );
- }
- }
- // Legend makes sense only with more than one dataset.
- legend.innerHTML = '';
- }
- // Render Bar chart.
- if ( chartType == 'bar' ) {
- chart.Bar( data, config.Bar );
- }
- // Render Line chart.
- else if ( chartType == 'line' ) {
- chart.Line( data, config.Line );
- }
- // Render Line chart.
- else if ( chartType == 'polar' ) {
- //chart.PolarArea( values );
- legend.innerHTML = chart.PolarArea( values, config.PolarArea ).generateLegend();
- }
- // Render Pie chart and legend.
- else if ( chartType == 'pie' ) {
- legend.innerHTML = chart.Pie( values, config.Pie ).generateLegend();
- }
- // Render Doughnut chart and legend.
- else {
- legend.innerHTML = chart.Doughnut( values, config.Doughnut ).generateLegend();
- }
- // ########## RENDER CHART END ##########
- }
- // Here we define the widget itself.
- editor.widgets.add( 'chart', {
- // The *label* for the button. The button *name* is assigned automatically based on the widget name.
- button: editor.lang.chart.chart,
- // Connect widget with a dialog defined earlier. So our toolbar button will open a dialog window.
- dialog: 'chart',
- // Based on this template a widget will be created automatically once user exits the dialog window.
- template: '<div class="chartjs" data-chart="bar" data-chart-height="' + chartDefaultHeight + '"><canvas height="' + chartDefaultHeight + '"></canvas><div class="chartjs-legend"></div></div>',
- // In order to provide styles (classes) for this widget through config.stylesSet we need to explicitly define the stylable elements.
- styleableElements: 'div',
- // Name to be displayed in the elements path (at the bottom of CKEditor),
- pathName: 'chart',
- // Run when initializing widget (thank you, captain obvious!).
- // It is common to use the init method to populate widget data with information loaded from the DOM.
- init: function() {
- // When an empty widget is initialized after clicking a button in the toolbar, we do not have yet chart values.
- if ( this.element.data( 'chart-value' ) ) {
- this.setData( 'values', JSON.parse( this.element.data( 'chart-value' ) ) );
- }
- // Chart is specified in a template, so it is available even in an empty widget.
- this.setData( 'chart', this.element.data( 'chart' ) );
- // Height is specified in a template, so it is available even in an empty widget.
- this.setData( 'height', this.element.data( 'chart-height' ) );
- // Pass the reference to this widget to the dialog. See "onOk" in the dialog definition, we needed widget there.
- this.on( 'dialog', function( evt ) {
- evt.data.widget = this;
- }, this );
- },
- // Run when widget data is changed (widget is rendered for the first time, inserted, changed).
- data: function() {
- // Just in case Chart.js was loaded asynchronously and is not available yet.
- if ( typeof Chart === 'undefined' )
- return;
- // It's hard to draw a chart without numbers.
- if ( !this.data.values )
- return;
- // It looks like Chart.js does not handle well updating charts.
- // When hovering over updated canvas old data is picked up sometimes, so we need to always replace an old canvas.
- var canvas = editor.document.createElement( 'canvas', { attributes: { height: this.data.height } } );
- canvas.replace( this.element.getChild( 0 ) );
- // Unify variable names with the one used in widget2chart.js.
- var legend = this.element.getChild( 1 ).$;
- canvas = canvas.$;
- // IE8 can't handle the next part (without the help of excanvas etc.).
- if ( !canvas.getContext )
- return;
- var data = this.data;
- // Without timeout the chart does not render immediately after inserting into the editing area.
- setTimeout( function() { renderChart( canvas, data, legend ) }, 0 );
- },
- // ACF settings. Without allowing elements introduced by this plugin, CKEditor built-in filter would remove it.
- allowedContent: 'div(!chartjs)[data-*];',
- requiredContent: 'div(chartjs)[data-chart-value,data-chart,data-chart-height]',
- // Executed when CKEditor loads content, when switching from source to wysiwyg mode. Makes HTML content a widget.
- upcast: function( element ) {
- if ( element.name == 'div' && element.hasClass( 'chartjs' ) ) {
- // Downcasted <div> could have contained some text like "chart" or which was there just to prevent <div>s from being deleted.
- // Get rid of it when upcasting.
- element.setHtml( '' );
- // Chart.js work on canvas elements, Prepare one.
- var canvas = new CKEDITOR.htmlParser.element( 'canvas', { height: element.attributes[ 'data-chart-height' ] } );
- element.add( canvas );
- // And make place for a legend.
- var div = new CKEDITOR.htmlParser.element( 'div', { 'class': 'chartjs-legend' } );
- element.add( div );
- return element;
- }
- },
- // Executed when CKEditor returns content, when switching from wysiwyg to source mode. Transforms a widget back to a downcasted form.
- downcast: function( element ) {
- var data = [];
- // Should not happen unless someone has accidentally messed up ACF rules.
- if ( !this.data.values )
- return;
- for ( var i = 0; i < this.data.values.length; i++ ) {
- // Get data from widget into an object in order to save it as data-chart-value attribute.
- // We could simply save this.data.values, but it contains some additional temporary data which we want to skip (like colors).
- data.push( {
- value: this.data.values[i].value,
- label: this.data.values[i].label
- } );
- }
- // Create the downcasted form of a widget (a simple <div>).
- var el = new CKEDITOR.htmlParser.element( 'div', {
- // We could pass here hardcoded "chartjs" class, but this way we would lose here all the classes applied through the Styles dropdown.
- // (In case someone defined his own styles for the chart widget)
- 'class': element.attributes['class'],
- 'data-chart': this.data.chart,
- 'data-chart-height': this.data.height,
- // Feature detection (editor.getSelectedHtml) to check if CKEditor 4.5+ is used.
- // CKEditor < 4.5 and CKEditor 4.5+ require different code due to https://dev.ckeditor.com/ticket/13105
- 'data-chart-value': editor.getSelectedHtml ? JSON.stringify( data ) : CKEDITOR.tools.htmlEncodeAttr( JSON.stringify( data ) )
- } );
- return el;
- }
- } );
- }
- } );
- } )();
- /**
- * The default chart height (in pixels) in the Edit Chart dialog window.
- *
- * // Set default height to 400px.
- * config.chart_height = 400;
- *
- * @cfg {Integer} [chart_height=300]
- * @member CKEDITOR.config
- */
- /**
- * The number of rows (items to enter) in the Edit Chart dialog window.
- *
- * // Set number of rows to 12.
- * config.chart_maxItems = 12;
- *
- * @cfg {Integer} [chart_maxItems=12]
- * @member CKEDITOR.config
- */
- /**
- * Colors used to draw charts. See <a href="http://www.chartjs.org/docs/#bar-chart-data-structure">Bar chart data structure</a> and
- * <a href="http://www.chartjs.org/docs/#doughnut-pie-chart-data-structure">Pie chart data structure</a>.
- *
- * config.chart_colors =
- * {
- * // Colors for Bar/Line chart.
- * fillColor: 'rgba(151,187,205,0.5)',
- * strokeColor: 'rgba(151,187,205,0.8)',
- * highlightFill: 'rgba(151,187,205,0.75)',
- * highlightStroke: 'rgba(151,187,205,1)',
- * // Colors for Doughnut/Pie/PolarArea charts.
- * data: [ '#B33131', '#B66F2D', '#B6B330', '#71B232', '#33B22D', '#31B272', '#2DB5B5', '#3172B6', '#3232B6', '#6E31B2', '#B434AF', '#B53071' ]
- * }
- *
- * @cfg {Array} chart_colors
- * @member CKEDITOR.config
- */
- /**
- * Chart.js configuration to use for Bar charts.
- *
- * @cfg {Object} [chart_configBar={ animation: false }]
- * @member CKEDITOR.config
- */
- /**
- * Chart.js configuration to use for Doughnut charts.
- *
- * @cfg {Object} [chart_configDoughnut={ animateRotate: false }]
- * @member CKEDITOR.config
- */
- /**
- * Chart.js configuration to use for Line charts.
- *
- * @cfg {Object} [chart_configLine={ animation: false }]
- * @member CKEDITOR.config
- */
- /**
- * Chart.js configuration to use for Pie charts.
- *
- * @cfg {Object} [chart_configPie={ animateRotate: false }]
- * @member CKEDITOR.config
- */
- /**
- * Chart.js configuration to use for PolarArea charts.
- *
- * @cfg {Object} [chart_configPolarArea={ animateRotate: false }]
- * @member CKEDITOR.config
- */
|