PluginProbe
M Chart / 1.2.1
M Chart v1.2.1
2.3.2 2.3.1 2.3 2.2.2 2.2.1 2.2 trunk 1.0 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.10 1.10.1 1.11 1.11.1 1.11.2 1.12 1.2 1.2.1 1.3 1.3.1 1.3.2 All 53 releases
m-chart / components / class-m-chart-highcharts.php

class-m-chart-highcharts.php in M Chart 1.2.1, at components/class-m-chart-highcharts.php

432 lines 12.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class M_Chart_Highcharts {
4 public $library = 'highcharts';
5 public $value_labels_limit = 15;
6 public $value_labels_div = 10;
7 public $original_labels;
8 public $post;
9 public $post_meta;
10 public $args;
11 public $type_options = array(
12 'line',
13 'spline',
14 'area',
15 'column',
16 'bar',
17 'pie',
18 );
19 public $theme_directories;
20
21 /**
22 * Constructor
23 */
24 public function __construct() {
25 add_action( 'm_chart_update_post_meta', array( $this, 'm_chart_update_post_meta' ), 10, 2 );
26
27 $this->theme_directories = array(
28 get_stylesheet_directory() . '/m-chart-highcharts-themes/', // Child theme
29 get_template_directory() . '/m-chart-highcharts-themes/', // Parent theme
30 __DIR__ . '/highcharts-themes/',
31 );
32 }
33
34 /**
35 * Get the necessary chart data for a given chart and assign it the right class vars
36 *
37 * @param int $post_id the WP ID for the chart post
38 * @param array $args any args we want to override the defaults for
39 */
40 public function get_chart_data( $post_id, $args ) {
41 $this->args = wp_parse_args( $args, m_chart()->get_chart_default_args );
42 $this->post = get_post( $post_id );
43 $this->post_meta = m_chart()->get_post_meta( $this->post->ID );
44 }
45
46 /**
47 * Get the chart options
48 *
49 * @return array an array of Highcharts wide options we want to set
50 */
51 public function get_chart_options() {
52 $chart_options = array(
53 'lang' => array(
54 'numericSymbols' => array(
55 /* Thousands */
56 'K',
57 /* Millions */
58 'M',
59 /* Billions */
60 'B',
61 /* Trillions */
62 'T',
63 ),
64 'thousandsSep' => array(
65 ',',
66 ),
67 ),
68 );
69
70 return apply_filters( 'm_chart_chart_options', $chart_options, $this->library );
71 }
72
73 /**
74 * Returns an arary of all settings and data needed to build a chart
75 *
76 * @param int $post_id WP post ID of the post you want chart args for
77 * @param array $args any args we want to override the defaults for
78 * @param bool $force optional param to force a rebuild of the data even if a cache was found
79 * @param bool $cache optional param to override the default behavior to cache the results
80 *
81 * @return string URL to the plugin directory with path if parameter was passed
82 */
83 public function get_chart_args( $post_id, $args, $force = false, $cache = true ) {
84 // There's a ton of work that goes into generating the chart args so we cache them
85 $cache_key = $post_id . '-chart-args';
86
87 if ( ! $force && $chart_args = wp_cache_get( $cache_key, m_chart()->slug ) ) {
88 // The width can be set via the args so we'll override whatever the cache has with the arg value
89 $chart_args['graph']['width'] = is_numeric( $this->args['width'] ) ? $this->args['width'] : '';
90 return $chart_args;
91 }
92
93 if ( ! $this->args || ! $this->post || ! $this->post_meta ) {
94 $this->get_chart_data( $post_id, $args );
95 }
96
97 // Run the parse class on the data
98 m_chart()->parse()->parse_data( $this->post_meta['data'], $this->post_meta['parse_in'] );
99
100 $chart_args = array(
101 'chart' => array(
102 'type' => $this->post_meta['type'],
103 'show_labels' => $this->post_meta['labels'] ? true : false,
104 'renderTo' => 'm-chart-' . $this->post->ID,
105 'height' => $this->post_meta['height'],
106 ),
107 'title' => array(
108 'text' => $this->esc_title( apply_filters( 'the_title', $this->post->post_title ) ),
109 ),
110 'subtitle' => array(
111 'text' => $this->esc_title( $this->post_meta['subtitle'] ),
112 ),
113 'legend' => array(
114 'enabled' => $this->post_meta['legend'] ? true : false,
115 ),
116 'credits' => array(
117 'href' => $this->post_meta['source_url'],
118 'text' => $this->post_meta['source'],
119 ),
120 'exporting' => array(
121 'enabled' => false,
122 ),
123 'plotOptions' => array(
124 'series' => array(
125 'dataLabels' => array(
126 'enabled' => $this->post_meta['labels'] ? true : false,
127 ),
128 'connectNulls' => true,
129 ),
130 $this->post_meta['type'] => array(
131 'dataLabels' => array(
132 'enabled' => $this->post_meta['labels'] ? true : false,
133 ),
134 ),
135 ),
136 );
137
138 // We don't want to set a width unless an explicit width was given
139 if ( is_numeric( $this->args['width'] ) ) {
140 $chart_args['chart']['width'] = $this->args['width'];
141 }
142
143 // Forcing a minimum value of 0 prevents the built in fudging which sometimes looks weird
144 if ( $this->post_meta['y_min'] ) {
145 $chart_args['yAxis']['min'] = 0;
146 }
147
148 // The x axis values need to be set or else you end up with a single notch :P
149 $chart_args['xAxis']['categories'] = $this->get_value_labels_array();
150
151 $chart_args = $this->add_axis_labels( $chart_args );
152 $chart_args = $this->add_data_sets( $chart_args );
153
154 // Add prefix/suffix if appropriate
155 if ( '' != m_chart()->parse()->data_prefix ) {
156 $chart_args['tooltip']['valuePrefix'] = m_chart()->parse()->data_prefix;
157 }
158
159 if ( '' != m_chart()->parse()->data_suffix ) {
160 $chart_args['tooltip']['valueSuffix'] = m_chart()->parse()->data_suffix;
161 }
162
163 $chart_args['plotOptions']['series']['dataLabels']['format'] = m_chart()->parse()->data_prefix . '{y:,f}' . m_chart()->parse()->data_suffix;
164
165 // Apply the theme
166 if ( $theme = $this->get_theme( $this->post_meta['theme'] ) ) {
167 $chart_args = array_merge( $chart_args, $theme );
168 }
169
170 $chart_args = apply_filters( 'm_chart_chart_args', $chart_args, $this->post, $this->post_meta, $this->args );
171
172 // Set the cache, we'll regenerate this when someone updates the post
173 if ( $cache ) {
174 wp_cache_set( $cache_key, $chart_args, m_chart()->slug );
175 }
176
177 // Clear out all of the class vars so the next chart instance starts fresh
178 unset( $this->args, $this->post, $this->post_meta );
179
180 return $chart_args;
181 }
182
183 /**
184 * Hook to the m_chart_update_post_meta action and refresh the chart args cache
185 *
186 * @param int $post_id WP post ID of the post you want chart args for
187 * @param array $parsed_meta the parsed chart meta passed by the action hook
188 */
189 public function m_chart_update_post_meta( $post_id, $parsed_meta ) {
190 // Refresh arg cache
191 $this->args = $this->get_chart_default_args;
192 $this->post = (object) array(
193 'ID' => $post_id,
194 );
195 $this->post_meta = $parsed_meta;
196
197 $this->get_chart_args( $post_id, array(), true );
198 }
199
200 /**
201 * Returns the value labels array
202 *
203 * @return array an array of the value labels need for the active chart
204 */
205
206 public function get_value_labels_array() {
207 $value_labels = m_chart()->parse()->value_labels;
208
209 if ( isset( $value_labels['first_column'] ) ) {
210 $label_key = 'rows' == $this->post_meta['parse_in'] ? 'first_row' : 'first_column';
211
212 return $value_labels[ $label_key ];
213 }
214
215 return $value_labels;
216 }
217
218 /**
219 * Handle adding units to axis labels and/or flipping axis labels on bar chart
220 *
221 * @param array the current array of chart args
222 *
223 * @return array the chart args array with axis labels (and units) added to it
224 */
225 public function add_axis_labels( $chart_args ) {
226 $chart_args['xAxis']['title']['text'] = $this->esc_title( $this->post_meta['x_title'] );
227
228 // We've got x axis units so we'll add them to the axis label
229 if ( '' != $this->post_meta['x_units'] ) {
230 $units = get_term_by( 'slug', $this->post_meta['x_units'], m_chart()->slug . '-units' );
231 $x_units = '' != $this->post_meta['x_title'] ? ' (' . $units->name . ')' : $units->name;
232
233 $chart_args['xAxis']['title']['text'] .= $x_units;
234 }
235
236 $chart_args['yAxis']['title']['text'] = $this->esc_title( $this->post_meta['y_title'] );
237
238 // We've got y axis units so we'll add them to the axis label
239 if ( $this->post_meta['y_units'] != '' ) {
240 $units = get_term_by( 'slug', $this->post_meta['y_units'], m_chart()->slug . '-units' );
241 $y_units = '' != $this->post_meta['y_title'] ? ' (' . $units->name . ')' : $units->name;
242
243 $chart_args['yAxis']['title']['text'] .= $y_units;
244 }
245
246 // Bar charts are just rotated column charts in Highcharts so we flip things around to keep the UI consistent
247 if ( 'bar' == $this->post_meta['type'] ) {
248 $x_title = $chart_args['xAxis']['title']['text'];
249
250 $chart_args['xAxis']['title']['text'] = $chart_args['yAxis']['title']['text'];
251 $chart_args['yAxis']['title']['text'] = $x_title;
252 }
253
254 return $chart_args;
255 }
256
257 /**
258 * Handle adding data sets (series in Highcharts terminology) to the chart args
259 *
260 * @param array the current array of chart args
261 *
262 * @return array the chart args array with data sets added to it
263 */
264 public function add_data_sets( $chart_args ) {
265 // When Highcharts encounters an empty data value it stops so we set them to NULL
266 $data_array = array_map( array( $this, 'fix_null_values' ), m_chart()->parse()->set_data );
267
268 if ( 'pie' != $this->post_meta['type'] && 'both' == m_chart()->parse()->value_labels_position ) {
269 $set_data = array();
270
271 $label_key = ( $this->post_meta['parse_in'] == 'rows' ) ? 'first_column' : 'first_row';
272
273 foreach ( $data_array as $key => $data_chunk ) {
274 $set_data[ $key ] = array(
275 'name' => m_chart()->parse()->value_labels[ $label_key ][ $key ],
276 'data' => array(),
277 );
278
279 foreach ( $data_chunk as $data ) {
280 $set_data[ $key ]['data'][] = $data;
281 }
282 }
283
284 $chart_args['series'] = $set_data;
285 }
286 else
287 {
288 $new_data_array = array();
289
290 foreach ( $chart_args['xAxis']['categories'] as $key => $label ) {
291 $new_data_array[ $key ] = array(
292 $label,
293 $data_array[ $key ],
294 );
295 }
296
297 if ( 'pie' == $this->post_meta['type'] ) {
298 // Don't need these anymore for pie charts
299 unset( $chart_args['xAxis']['categories'] );
300 }
301
302 $chart_args['series'] = array(
303 array(
304 'type' => $this->post_meta['type'],
305 'showInLegend' => true,
306 'data' => $new_data_array,
307 ),
308 );
309
310 $chart_args['tooltip'] = array(
311 'pointFormat' => '<b>{point.y}</b>',
312 );
313 }
314
315 return $chart_args;
316 }
317
318 /**
319 * Helper function escapes and modifies text/title values so they work in Highcharts
320 *
321 * @param string an string you want to use in Highcharts
322 *
323 * @return string an escaped and modified string
324 */
325 public function esc_title( $string ) {
326 $string = html_entity_decode( $string, ENT_QUOTES );
327
328 $find = array(
329 "\n",
330 "\r",
331 '<br><br>',
332 '—',
333 '–',
334 );
335
336 $replace = array(
337 '<br />',
338 '<br />',
339 '<br />',
340 '-',
341 '-',
342 );
343
344 $string = str_replace( $find, $replace, $string );
345
346 // @TODO: See if this addslashes/stripslashes is still necessary (need to remember why I did it first...)
347 return addslashes( stripslashes( $string ) );
348 }
349
350 /**
351 * Helper function sets empty values to NULL so that Highcharts handles them correctly.
352 *
353 * @param string/int a data value
354 *
355 * @return int/null the integer value or NULL if the value was not numeric
356 */
357 public function fix_null_values( $value ) {
358 if ( is_array( $value ) ) {
359 return array_map( array( $this, 'fix_null_values' ), $value );
360 }
361
362 if ( ! is_numeric( $value ) ) {
363 return null;
364 }
365
366 return $value;
367 }
368
369 public function get_themes() {
370 $themes = array();
371
372 foreach ( $this->theme_directories as $directory ) {
373 $themes = array_merge( $themes, $this->_get_themes_readdir( $directory ) );
374 }
375
376 return $themes;
377 }
378
379 private function get_theme( $slug ) {
380 foreach ( $this->theme_directories as $directory ) {
381 if ( ! $themes = $this->_get_themes_readdir( $directory ) ) {
382 continue;
383 } // END if
384
385 foreach ( $themes as $theme )
386 {
387 if ( $theme->slug == $slug )
388 {
389 return $theme->options;
390 } // END if
391 } // END foreach
392 } // END foreach
393
394 return false;
395 }
396
397 private function _get_themes_readdir( $theme_base ) {
398 // Sanity check to make sure we have a real directory
399 if ( ! is_dir( $theme_base ) ) {
400 return array();
401 }
402
403 $theme_dir = new DirectoryIterator( $theme_base );
404 $themes = array();
405
406 foreach ( $theme_dir as $file ) {
407 if ( ! $file->isFile() || 'php' != $file->getExtension() ) {
408 continue;
409 }
410
411 $theme_data = implode( '', file( $theme_base . $file ) );
412
413 if ( preg_match( '|Theme Name:(.*)$|mi', $theme_data, $name ) ) {
414 $name = trim( _cleanup_header_comment( $name[1] ) );
415 } // END if
416
417 if ( isset( $name ) && '' != $name )
418 {
419 $file = basename( $file );
420
421 $themes[ $file ] = (object) array(
422 'name' => $name,
423 'slug' => substr( $file, 0, -4 ),
424 'file' => $file,
425 'options' => require $theme_base . $file,
426 );
427 } // END if
428 } // END foreach
429
430 return $themes;
431 }
432 }