theme_directories = array( get_stylesheet_directory() . '/m-chart-highcharts-themes/', // Child theme get_template_directory() . '/m-chart-highcharts-themes/', // Parent theme __DIR__ . '/highcharts-themes/', ); } /** * Get the necessary chart data for a given chart and assign it the right class vars * * @param int $post_id the WP ID for the chart post * @param array $args any args we want to override the defaults for */ public function get_chart_data( $post_id, $args ) { $this->args = wp_parse_args( $args, m_chart()->get_chart_default_args ); $this->post = get_post( $post_id ); $this->post_meta = m_chart()->get_post_meta( $this->post->ID ); } /** * Get the chart options * * @return array an array of Highcharts wide options we want to set */ public function get_chart_options() { $chart_options = array( 'lang' => array( 'numericSymbols' => array( /* Thousands */ 'K', /* Millions */ 'M', /* Billions */ 'B', /* Trillions */ 'T', ), 'thousandsSep' => array( ',', ), ), ); return apply_filters( 'm_chart_chart_options', $chart_options, $this->library ); } /** * Returns an arary of all settings and data needed to build a chart * * @param int $post_id WP post ID of the post you want chart args for * @param array $args any args we want to override the defaults for * @param bool $force optional param to force a rebuild of the data even if a cache was found * @param bool $cache optional param to override the default behavior to cache the results * * @return string URL to the plugin directory with path if parameter was passed */ public function get_chart_args( $post_id, $args, $force = false, $cache = true ) { // There's a ton of work that goes into generating the chart args so we cache them $cache_key = $post_id . '-chart-args'; if ( ! $force && $chart_args = wp_cache_get( $cache_key, m_chart()->slug ) ) { // The width can be set via the args so we'll override whatever the cache has with the arg value $chart_args['graph']['width'] = isset( $this->args['width'] ) && is_numeric( $this->args['width'] ) ? $this->args['width'] : ''; return $chart_args; } if ( ! $this->args || ! $this->post || ! $this->post_meta ) { $this->get_chart_data( $post_id, $args ); } // Run the parse class on the data m_chart()->parse()->parse_data( $this->post_meta['data'], $this->post_meta['parse_in'] ); $chart_args = array( 'chart' => array( 'type' => $this->post_meta['type'], 'show_labels' => $this->post_meta['labels'] ? true : false, 'renderTo' => 'm-chart-' . $this->post->ID, 'height' => $this->post_meta['height'], ), 'title' => array( 'text' => $this->esc_title( apply_filters( 'the_title', $this->post->post_title ) ), ), 'subtitle' => array( 'text' => $this->esc_title( $this->post_meta['subtitle'] ), ), 'legend' => array( 'enabled' => $this->post_meta['legend'] ? true : false, ), 'credits' => array( 'href' => $this->post_meta['source_url'], 'text' => $this->post_meta['source'], ), 'exporting' => array( 'enabled' => false, ), 'plotOptions' => array( 'series' => array( 'dataLabels' => array( 'enabled' => $this->post_meta['labels'] ? true : false, ), 'connectNulls' => true, ), $this->post_meta['type'] => array( 'dataLabels' => array( 'enabled' => $this->post_meta['labels'] ? true : false, ), ), ), ); // We don't want to set a width unless an explicit width was given if ( is_numeric( $this->args['width'] ) ) { $chart_args['chart']['width'] = $this->args['width']; } // Forcing a minimum value of 0 prevents the built in fudging which sometimes looks weird if ( $this->post_meta['y_min'] ) { $chart_args['yAxis']['min'] = 0; } // The x axis values need to be set or else you end up with a single notch :P $chart_args['xAxis']['categories'] = $this->get_value_labels_array(); $chart_args = $this->add_axis_labels( $chart_args ); $chart_args = $this->add_data_sets( $chart_args ); // Add prefix/suffix if appropriate if ( '' != m_chart()->parse()->data_prefix ) { $chart_args['tooltip']['valuePrefix'] = m_chart()->parse()->data_prefix; } if ( '' != m_chart()->parse()->data_suffix ) { $chart_args['tooltip']['valueSuffix'] = m_chart()->parse()->data_suffix; } $chart_args['plotOptions']['series']['dataLabels']['format'] = m_chart()->parse()->data_prefix . '{y:,f}' . m_chart()->parse()->data_suffix; // Apply the theme if ( $theme = $this->get_theme( $this->post_meta['theme'] ) ) { $chart_args = array_merge( $chart_args, $theme ); } $chart_args = apply_filters( 'm_chart_chart_args', $chart_args, $this->post, $this->post_meta, $this->args ); // Set the cache, we'll regenerate this when someone updates the post if ( $cache ) { wp_cache_set( $cache_key, $chart_args, m_chart()->slug ); } // Clear out all of the class vars so the next chart instance starts fresh $this->args = null; $this->post = null; $this->post_meta = null; return $chart_args; } /** * Hook to the m_chart_update_post_meta action and refresh the chart args cache * * @param int $post_id WP post ID of the post you want chart args for * @param array $parsed_meta the parsed chart meta passed by the action hook */ public function m_chart_update_post_meta( $post_id, $parsed_meta ) { // Refresh arg cache $this->args = $this->get_chart_default_args; $this->post = (object) array( 'ID' => $post_id, ); $this->post_meta = $parsed_meta; $this->get_chart_args( $post_id, array(), true ); } /** * Returns the value labels array * * @return array an array of the value labels need for the active chart */ public function get_value_labels_array() { $value_labels = m_chart()->parse()->value_labels; if ( isset( $value_labels['first_column'] ) ) { $label_key = 'rows' == $this->post_meta['parse_in'] ? 'first_row' : 'first_column'; return $value_labels[ $label_key ]; } return $value_labels; } /** * Handle adding units to axis labels and/or flipping axis labels on bar chart * * @param array the current array of chart args * * @return array the chart args array with axis labels (and units) added to it */ public function add_axis_labels( $chart_args ) { $chart_args['xAxis']['title']['text'] = $this->esc_title( $this->post_meta['x_title'] ); // We've got x axis units so we'll add them to the axis label if ( '' != $this->post_meta['x_units'] ) { $units = get_term_by( 'slug', $this->post_meta['x_units'], m_chart()->slug . '-units' ); $x_units = '' != $this->post_meta['x_title'] ? ' (' . $units->name . ')' : $units->name; $chart_args['xAxis']['title']['text'] .= $x_units; } $chart_args['yAxis']['title']['text'] = $this->esc_title( $this->post_meta['y_title'] ); // We've got y axis units so we'll add them to the axis label if ( $this->post_meta['y_units'] != '' ) { $units = get_term_by( 'slug', $this->post_meta['y_units'], m_chart()->slug . '-units' ); $y_units = '' != $this->post_meta['y_title'] ? ' (' . $units->name . ')' : $units->name; $chart_args['yAxis']['title']['text'] .= $y_units; } // Bar charts are just rotated column charts in Highcharts so we flip things around to keep the UI consistent if ( 'bar' == $this->post_meta['type'] ) { $x_title = $chart_args['xAxis']['title']['text']; $chart_args['xAxis']['title']['text'] = $chart_args['yAxis']['title']['text']; $chart_args['yAxis']['title']['text'] = $x_title; } return $chart_args; } /** * Handle adding data sets (series in Highcharts terminology) to the chart args * * @param array the current array of chart args * * @return array the chart args array with data sets added to it */ public function add_data_sets( $chart_args ) { // When Highcharts encounters an empty data value it stops so we set them to NULL $data_array = array_map( array( $this, 'fix_null_values' ), m_chart()->parse()->set_data ); if ( 'pie' != $this->post_meta['type'] && 'both' == m_chart()->parse()->value_labels_position ) { $set_data = array(); $label_key = ( $this->post_meta['parse_in'] == 'rows' ) ? 'first_column' : 'first_row'; foreach ( $data_array as $key => $data_chunk ) { $set_data[ $key ] = array( 'name' => m_chart()->parse()->value_labels[ $label_key ][ $key ], 'data' => array(), ); foreach ( $data_chunk as $data ) { $set_data[ $key ]['data'][] = $data; } } $chart_args['series'] = $set_data; } else { $new_data_array = array(); foreach ( $chart_args['xAxis']['categories'] as $key => $label ) { $new_data_array[ $key ] = array( $label, $data_array[ $key ], ); } if ( 'pie' == $this->post_meta['type'] ) { // Don't need these anymore for pie charts unset( $chart_args['xAxis']['categories'] ); } $chart_args['series'] = array( array( 'type' => $this->post_meta['type'], 'showInLegend' => true, 'data' => $new_data_array, ), ); $chart_args['tooltip'] = array( 'pointFormat' => '{point.y}', ); } return $chart_args; } /** * Helper function escapes and modifies text/title values so they work in Highcharts * * @param string an string you want to use in Highcharts * * @return string an escaped and modified string */ public function esc_title( $string ) { $string = html_entity_decode( $string, ENT_QUOTES ); $find = array( "\n", "\r", '

', '—', '–', ); $replace = array( '
', '
', '
', '-', '-', ); $string = str_replace( $find, $replace, $string ); // @TODO: See if this addslashes/stripslashes is still necessary (need to remember why I did it first...) return addslashes( stripslashes( $string ) ); } /** * Helper function sets empty values to NULL so that Highcharts handles them correctly. * * @param string/int a data value * * @return int/null the integer value or NULL if the value was not numeric */ public function fix_null_values( $value ) { if ( is_array( $value ) ) { return array_map( array( $this, 'fix_null_values' ), $value ); } if ( ! is_numeric( $value ) ) { return null; } return $value; } /** * Get all themes available from the various theme directories * * @return array an array of themes */ public function get_themes() { $themes = array(); foreach ( $this->theme_directories as $directory ) { $themes = array_merge( $themes, $this->_get_themes_readdir( $directory ) ); } return $themes; } /** * Returns the theme options for a given theme * * @param string a theme slug * * @return string/boolean requested theme options or false if they could not be found */ private function get_theme( $slug ) { foreach ( $this->theme_directories as $directory ) { if ( ! $themes = $this->_get_themes_readdir( $directory ) ) { continue; } // END if foreach ( $themes as $theme ) { if ( $theme->slug == $slug ) { return $theme->options; } // END if } // END foreach } // END foreach return false; } /** * Get all themes from a given directory * * @param string a path to a server directory * * @return array an array of all the themes available in a given directory */ private function _get_themes_readdir( $theme_base ) { // Sanity check to make sure we have a real directory if ( ! is_dir( $theme_base ) ) { return array(); } $theme_dir = new DirectoryIterator( $theme_base ); $themes = array(); foreach ( $theme_dir as $file ) { if ( ! $file->isFile() || 'php' != $file->getExtension() ) { continue; } $theme_data = implode( '', file( $theme_base . $file ) ); if ( preg_match( '|Theme Name:(.*)$|mi', $theme_data, $name ) ) { $name = trim( _cleanup_header_comment( $name[1] ) ); } // END if if ( isset( $name ) && '' != $name ) { $file = basename( $file ); $themes[ $file ] = (object) array( 'name' => $name, 'slug' => substr( $file, 0, -4 ), 'file' => $file, 'options' => require $theme_base . $file, ); } // END if } // END foreach return $themes; } }