count posts from custom taxonomy terms by year

EDIT 2

Here is another version of the code in EDIT 1. This code is about much more faster. Here is my test between the code in EDIT 1 and EDIT 2

  • EDIT 1 Database query time = +/- 0.25 and Database queries = 69

  • EDIT 2 Database query time = +/- 0.07 and Database queries = 29

Here is the code

<?php

 $oldest = get_posts( 'post_type=post&post_status=publish&posts_per_page=1&order=ASC' );
    $oldest_date = $oldest[0]->post_date;

    $first_date = date('Y', strtotime($oldest_date));
    $todays_date = date('Y');

    $year_range = range($todays_date, $first_date);

    foreach ($year_range as $year) { // dynamic year-based tables
        echo '<h2>' . $year . '</h2>';
        $terms = get_terms('category');

        $term_slugs = array();

        if ( !empty( $terms ) && !is_wp_error( $terms ) ) { // table body

            foreach ( $terms as $key=>$term){
                $term_slugs[$key] = $term->slug;
            }

            echo '
                <table class="statistics">
                <tbody>
                ';
            echo '
                <thead>
                    <tr>
                        <td>Taxonomy Term</td>
                        <td>Percentage</td>
                        <td class="chart-count">Count</td>
                    </tr>
                </thead>
                ';

            $posts_count = array(); // Holds all term post counts in an array
            $terms_array = array();  // Holds all term names in an array 

                $args = array(
                    'posts_per_page'    => -1,
                    'post_type'         => 'post',
                    'post_status'       => 'publish',
                    'year'              => $year,
                    'tax_query' => array(
                        array(
                            'taxonomy'          => 'category',
                            'field'             => 'slug',
                            'terms'             => $term_slugs,
                            'include_children'  => false 
                        ),
                    ),
                );

                $yearly_posts_per_term = new WP_Query($args);
                    $posts_count[] = $yearly_posts_per_term->post_count; //Collects post counts and send them to an array

                if($yearly_posts_per_term->have_posts()):
                    while($yearly_posts_per_term->have_posts()): $yearly_posts_per_term->the_post();

                        $terms = get_the_terms( $post->ID, 'category' );

                        if ( $terms && ! is_wp_error( $terms ) ) {
                            foreach ( $terms as $term ) {
                                $terms_array[] = $term->slug;
                            }
                        } 

                    endwhile;
                endif;

        }

        $total_posts = array_sum($posts_count); //Use array_sum to add up all the separate post counts

        $result = array_count_values($terms_array);

        foreach ($result as $term_name=>$count) {

            $percentage = round( (($count / $total_posts)*100), 2 ); //Calculate the percentages of each term post cound to total year post count

            echo '
                    <tr>
                        <td class="chart-item">'.$term_name.'</td>
                        <td class="chart-visual"><div class="chart-bar" style="width:'.$percentage.'%;"></div> '.$percentage.'%</td>
                        <td class="chart-count">'.$count.'</td>
                    </tr>
                ';
        }   

            echo '
                <tfoot>
                    <tr>
                    <td colspan="2">Posts total</td>
                    <td class="chart-count">'.$total_posts.'</td>
                    </tr>
                </tfoot>
                ';

            echo '
                </tbody>
                </table>
                '; 
    } // end of year-based list

?>

This renders the same output as the table in EDIT 1, except, it does not show empty terms, only terms with posts are shown

enter image description here

EDIT 1

From your edited question, here is the strip down of the new code. I had to scrap one or two things here and rearrange some elements to make this work. The big challenge here was to calculate the percentages, because the variables that is used to calculate this lived in separate foreach loops. Variables within foreach loops only lives within that foreach, not outsie

The big changes to the code (from my original answer, @deflime code and your integrated code) from your edit are

  • Moved the two tables holding the total posts count and the percentages and term names to outside just below the $terms foreach loop

  • Pushed the term names and posts count from each term to an array outside the $terms foreach loop

  • Scrapped @deflime code, removed $total_posts = 0; and only kept and modified $percentage = round( (($yearly_posts_per_term->post_count / $total_posts)*100), 2 );

  • Used array_sum to get the total amount of posts for the year from the array of post counts per term

  • Used array_combine to create an associative array with the term names and post counts from each term

  • Finally, I used a foreach loop to get each term name and the associated post count, to feed that back into the table

Here is the final code

<?php

 $oldest = get_posts( 'post_type=post&post_status=publish&posts_per_page=1&order=ASC' );
    $oldest_date = $oldest[0]->post_date;

    $first_date = date('Y', strtotime($oldest_date));
    $todays_date = date('Y');

    $year_range = range($todays_date, $first_date);

    foreach ($year_range as $year) { // dynamic year-based tables
        echo '<h2>' . $year . '</h2>';
        $terms = get_terms('category');

        if ( !empty( $terms ) && !is_wp_error( $terms ) ) { // table body

            echo '
                <table class="statistics">
                <tbody>
                ';
            echo '
                <thead>
                    <tr>
                        <td>Taxonomy Term</td>
                        <td>Percentage</td>
                        <td class="chart-count">Count</td>
                    </tr>
                </thead>
                ';

            $posts_count = array(); // Holds all term post counts in an array
            $term_names = array();  // Holds all term names in an array

            foreach($terms as $term) {
                $term_names[] = $term->name; //Collects term names and send them to an array

                $args = array(
                    'posts_per_page'    => -1,
                    'post_type'         => 'post',
                    'post_status'       => 'publish',
                    'year'              => $year,
                    'tax_query' => array(
                        array(
                            'taxonomy'          => 'category',
                            'field'             => 'slug',
                            'terms'             => $term->slug,
                            'include_children'  => false 
                        ),
                    ),
                );

                $yearly_posts_per_term = new WP_Query($args);
                    $posts_count[] = $yearly_posts_per_term->post_count; //Collects post counts and send them to an array

            } // endforeach

            unset($term);

        }

        $total_posts = array_sum($posts_count); //Use array_sum to add up all the separate post counts

        $combine = array_combine($term_names,$posts_count); //Use array_combine to combine term names and post counts into assosiative array

        foreach ($combine as $term_name=>$count) {

            $percentage = round( (($count / $total_posts)*100), 2 ); //Calculate the percentages of each term post cound to total year post count

            echo '
                    <tr>
                        <td class="chart-item">'.$term_name.'</td>
                        <td class="chart-visual"><div class="chart-bar" style="width:'.$percentage.'%;"></div> '.$percentage.'%</td>
                        <td class="chart-count">'.$count.'</td>
                    </tr>
                ';
        }   

            echo '
                <tfoot>
                    <tr>
                    <td colspan="2">Posts total</td>
                    <td class="chart-count">'.$total_posts.'</td>
                    </tr>
                </tfoot>
                ';

            echo '
                </tbody>
                </table>
                '; 
    } // end of year-based list

?>

Please note As in my original answer, I’ve changed the post type to post and taxonomy to category for the purpose of testing.

Your end result is a table looking like this. Please note All my term names are in Afrikaans as I tested it on my test site that is in Afrikaans.

enter image description here

ORIGINAL ANSWER

This is a very rough draft of an idea I had on how to do this. I’ve included no HTML mark-up and used the default post type post and the build-in taxonomy category to test the code.

Here is how I constructed the complete query

  • First, get the date of the oldest post (should be the first post) on the site. This is done by a simple get_posts query. Modify to suite your needs

    $oldest = get_posts( 'post_status=publish&posts_per_page=1&order=ASC' );
        $oldest_date = $oldest[0]->post_date;
    
  • Next, strip the returned date to only get the year from the post date. Use strtotime() function to convert the year into a Unix timestamp

    $first_date = date('Y', strtotime($oldest_date));
    
  • Return the current date, you would want only the year. Use the date() function

    $current_date = date('Y');
    
  • Return both dates to the range() function to print a range of years between the two dates

    $year_range = range($current_date, $first_date);
    
  • Add these ranges back into a foreach loop to get your posts in year based lists

  • I’ve used get_terms() to geta list of all the available terms of the taxonomy in question

    $terms = get_terms('category');
    
  • Now, all of this info needs to be fed back into a tax_query using WP_Query

    $args = array(
            'posts_per_page'    => -1,
            'post_type'         => 'post',
            'post_status'       => 'publish',
            'year'              => $year,
            'tax_query' => array(
                array(
                    'taxonomy' => 'category',
                    'field'    => 'slug',
                    'terms'    => $term->slug
                ),
            ),
        );
    
    $posts = new WP_Query($args);
    
  • Lastly, you want to return the term name and the amount of posts per term

    echo $term->name . '(' . $posts->post_count . ')';
    

Now all together!!

<?php
$oldest = get_posts( 'post_status=publish&posts_per_page=1&order=ASC' );
    $oldest_date = $oldest[0]->post_date;

$first_date = date('Y', strtotime($oldest_date));
$current_date = date('Y');

$year_range = range($current_date, $first_date);

foreach ($year_range as $year) {
    echo $year;

    $terms = get_terms('category');
    if ( !empty( $terms ) && !is_wp_error( $terms ) ){

        foreach ( $terms as $term ) {
            $args = array(
                'posts_per_page'    => -1,
                'post_type'         => 'post',
                'post_status'       => 'publish',
                'year'              => $year,
                'tax_query' => array(
                    array(
                        'taxonomy' => 'category',
                        'field'    => 'slug',
                        'terms'    => $term->slug
                    ),
                ),
            );

            $posts = new WP_Query($args);

                echo $term->name . '(' . $posts->post_count . ')';
        }
    }
}
 ?>

As said, this can be refined, so take this idea and code and adapt and modify as you see fit. Hope this helps.

Leave a Comment