<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>graphs &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/graphs/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Wed, 18 Mar 2026 08:55:15 +0000</lastBuildDate>
	<language>en-GB</language>
	<copyright>© Terence Eden. 🄯 CC BY-SA. See https://shkspr.mobi/blog/copyright-and-copyleft/</copyright>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>graphs &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[Graphing the connections between my blog posts]]></title>
		<link>https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/</link>
					<comments>https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#comments</comments>
				<dc:creator><![CDATA[Terence Eden]]></dc:creator>
		<pubDate>Thu, 09 Jan 2025 12:34:56 +0000</pubDate>
		<category><![CDATA[blogging]]></category><category><![CDATA[graphs]]></category><category><![CDATA[HTML]]></category><category><![CDATA[PHP]]></category><category><![CDATA[WordPress]]></category>		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=55159</guid>

					<description><![CDATA[I love ripping off good ideas from other people&#039;s blogs.  I was reading Alvaro Graves-Fuenzalida&#039;s blog when I saw this nifty little force-directed graph:    When zoomed in, it shows the relation between posts and tags.    In this case, I can see that the posts about Small Gods and Pyramids both share the tags of Discworld, Fantasy, and Book Review. But only Small Gods has the tag of Religion. …]]></description>
										<content:encoded>&lt;p&gt;I love ripping off good ideas from other people's blogs.  I was reading &lt;a href="https://stuff.graves.cl/posts/2024-03-05_20_41-book-review---small-gods-by-terry-pratchett.html"&gt;Alvaro Graves-Fuenzalida's blog&lt;/a&gt; when I saw this nifty little force-directed graph:&lt;/p&gt;

&lt;img src="https://shkspr.mobi/blog/wp-content/uploads/2024/12/Graph-fs8.png" alt="A graph of interconnected nodes." width="800" height="600" class="aligncenter size-full wp-image-55160"&gt;

&lt;p&gt;When zoomed in, it shows the relation between posts and tags.&lt;/p&gt;

&lt;img src="https://shkspr.mobi/blog/wp-content/uploads/2024/12/Graph-detail-fs8.png" alt="Text labels on the nodes show that the two of the posts share a common tag." width="1600" height="1200" class="aligncenter size-full wp-image-55161"&gt;

&lt;p&gt;In this case, I can see that the posts about Small Gods and Pyramids both share the tags of Discworld, Fantasy, and Book Review. But only Small Gods has the tag of Religion.&lt;/p&gt;

&lt;p&gt;Isn't that cool! It is a native feature of &lt;a href="https://quartz.jzhao.xyz/features/graph-view"&gt;Quartz's GraphView&lt;/a&gt;. How can I build something like that for my WordPress blog?&lt;/p&gt;

&lt;h2 id="aim"&gt;&lt;a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#aim"&gt;Aim&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Create an interactive graph which shows the relationship between a post, its links, and their tags.&lt;/p&gt;

&lt;p&gt;It will end up looking something like this:&lt;/p&gt;

&lt;img src="https://shkspr.mobi/blog/wp-content/uploads/2024/12/fdg.png" alt="A force directed graph showing how four different posts link to each other and how their hashtags relate." width="1188" height="1088" class="aligncenter size-full wp-image-55169"&gt;

&lt;p&gt;You can &lt;a href="https://gitlab.com/edent/blog-theme/-/blob/master/includes/graph.php"&gt;get the code&lt;/a&gt; or follow along to see how it works.&lt;/p&gt;

&lt;p&gt;This is a multi-stage process. Let's begin!&lt;/p&gt;

&lt;h2 id="what-we-need"&gt;&lt;a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#what-we-need"&gt;What We Need&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;When on a single Post, we need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The tags assigned to that Post.&lt;/li&gt;
&lt;li&gt;Internal links back to that Post.&lt;/li&gt;
&lt;li&gt;Internal links from that Post.&lt;/li&gt;
&lt;li&gt;The tags assigned to links to and from that Post.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="tags-assigned-to-that-post"&gt;&lt;a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#tags-assigned-to-that-post"&gt;Tags assigned to that Post.&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;This is pretty easy!  Using the &lt;a href="https://developer.wordpress.org/reference/functions/get_the_tag_list/"&gt;&lt;code&gt;get_the_tag_list()&lt;/code&gt; function&lt;/a&gt; we can, unsurprisingly, get all the tags associated with a post.&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-php"&gt;$post_tags_text = get_the_tag_list( "", ",", $ID );
$post_tags_array = explode( "," , $post_tags_text );
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That just gets the list of tag names. If we want the tag IDs as well, we need to use &lt;a href="https://developer.wordpress.org/reference/functions/get_the_tags/"&gt;the &lt;code&gt;get_the_tags()&lt;/code&gt; function&lt;/a&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-php"&gt;$post_tags = get_the_tags($ID);
$tags = array();
foreach($post_tags as $tag) {
    $tags[$tag-&amp;gt;term_id] = $tag-&amp;gt;name; 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="backlinks"&gt;&lt;a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#backlinks"&gt;Backlinks&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Internal links back to the Post is slightly trickier. WordPress doesn't save relational information like that. Instead, we get the Post's URl and &lt;a href="https://shkspr.mobi/blog/2023/10/displaying-internal-linkbacks-on-wordpress/"&gt;search for that in the database&lt;/a&gt;. Then we get the post IDs of all the posts which contain that string.&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-php"&gt;//  Get all the posts which link to this one, oldest first
$the_query = new WP_Query(
    array(
        's' =&amp;gt; $search_url,
        'post_type' =&amp;gt; 'post',
        "posts_per_page" =&amp;gt; "-1",
        "order" =&amp;gt; "ASC"
    )
);

//  Nothing to do if there are no inbound links
if ( !$the_query-&amp;gt;have_posts() ) {
    return;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="backlinks-tags"&gt;&lt;a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#backlinks-tags"&gt;Backlinks' Tags&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Once we have an array of posts which link back here, we can get their tags as above:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-php"&gt;//  Loop through the posts
while ( $the_query-&amp;gt;have_posts() ) {
    //  Set it up
    $the_query-&amp;gt;the_post();
    $id                  = get_the_ID();
    $title               = esc_html( get_the_title() );
    $url                 = get_the_permalink();
    $backlink_tags_text  = get_the_tag_list( "", ",", $ID );
    $backlink_tags_array = explode( "," , $backlink_tags_text );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="links-from-the-post"&gt;&lt;a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#links-from-the-post"&gt;Links from the Post&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Again, WordPress's lack of relational links is a weakness. In order to get internal links, we need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Render the HTML using all the filters&lt;/li&gt;
&lt;li&gt;Search for all &lt;code&gt;&amp;lt;a href="…"&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Extract the ones which start with the blog's domain&lt;/li&gt;
&lt;li&gt;Get those posts' IDs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rendering the content into HTML is done with:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-php"&gt;$content = apply_filters( "the_content", get_the_content( null, false, $ID ) );
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Searching for links is slightly more complex. The easiest way is to load the HTML into a DOMDocument, then extract all the anchors. All my blog posts start &lt;code&gt;/blog/YYYY&lt;/code&gt; so I can avoid selecting links to tags, uploaded files, or other things. Your blog may be different.&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-php"&gt;$dom = new DOMDocument();
libxml_use_internal_errors( true ); //  Suppress warnings from malformed HTML
$dom-&amp;gt;loadHTML( $content );
libxml_clear_errors();

$links = [];
foreach ( $dom-&amp;gt;getElementsByTagName( "a" ) as $anchor ) {
    $href = $anchor-&amp;gt;getAttribute( "href" );
    if (preg_match('/^https:\/\/shkspr\.mobi\/blog\/\d{4}$/', $href)) {
        $links[] = $href;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The ID of each post can be found with &lt;a href="https://developer.wordpress.org/reference/functions/url_to_postid/"&gt;the &lt;code&gt;url_to_postid()&lt;/code&gt; function&lt;/a&gt;. That means we can re-use the earlier code to see what tags those posts have.&lt;/p&gt;

&lt;h2 id="building-a-graph"&gt;&lt;a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#building-a-graph"&gt;Building a graph&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;OK, so we have all our constituent parts. Let's build a graph!&lt;/p&gt;

&lt;p&gt;Graphs consist of nodes (posts and tags) and edges (links between them). The exact format of the graph is going to depend on the graph library we use.&lt;/p&gt;

&lt;p&gt;I've decided to use &lt;a href="https://github.com/d3/d3-force?tab=readme-ov-file"&gt;D3.js's Force Graph&lt;/a&gt; as it is relatively simple and produces a reasonably good looking interactive SVG.&lt;/p&gt;

&lt;p&gt;Imagine there are two blog posts and two hashtags.&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-js"&gt;const nodes = [
    { id: 1, label: "Blog Post 1",    url: "https://example.com/post/1", group: "post" },
    { id: 2, label: "Blog Post 2",    url: "https://example.com/post/2", group: "post" },
    { id: 3, label: "hashtag",        url: "https://example.com/tag/3",  group: "tag"  },
    { id: 4, label: "anotherHashtag", url: "https://example.com/tag/4",  group: "tag"  },
];
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Blog Post 1 links to Blog Post 2.&lt;/li&gt;
&lt;li&gt;Blog Post 1 has a #hashtag.&lt;/li&gt;
&lt;li&gt;Both 1 &amp;amp; 2 share #anotherHashtag.&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code class="language-js"&gt;const links = [
    { source: 1, target: 2 },
    { source: 3, target: 1 },
    { source: 4, target: 1 },
    { source: 4, target: 2 },
];
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here's how to create a list of nodes and their links.  You will need to edit it for your own blog's peculiarities.&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-php"&gt;&amp;lt;?php 
// Load WordPress environment
require_once( "wp-load.php" );

//  Set up arrays for nodes and links
$nodes = array();
$links = array();

//  ID of the Post
$main_post_id = 12345;

//  Get the Post's details
$main_post_url   = get_permalink( $main_post_id );
$main_post_title = get_the_title( $main_post_id );

//  Function to add new nodes
function add_item_to_nodes( &amp;amp;$nodes, $id, $label, $url, $group ) {
    $nodes[] = [ 
        "id"    =&amp;gt; $id, 
        "label" =&amp;gt; $label, 
        "url"   =&amp;gt; $url, 
        "group" =&amp;gt; $group
    ];

}

//  Function to add new relationships
function add_relationship( &amp;amp;$links, $source, $target ) {
    $links[] = [
        "source" =&amp;gt; $source,
        "target" =&amp;gt; $target
    ];
}

//  Add Post to the nodes
add_item_to_nodes( $nodes, $main_post_id, $main_post_title, $main_post_url, "post" );

//  Get the tags of the Post
$main_post_tags = get_the_tags( $main_post_id );

//  Add the tags as nodes, and create links to main Post
foreach( $main_post_tags as $tag ) {
    $id   = $tag-&amp;gt;term_id;
    $name = $tag-&amp;gt;name;

    //  Add the node
    add_item_to_nodes( $nodes, $id, $name, "https://shkspr.mobi/blog/tag/" . $name, "tag" );
    //  Add the relationship
    add_relationship( $links, $id, $main_post_id );
}

//  Get all the posts which link to this one, oldest first
$the_query = new WP_Query(
    array(
        's'              =&amp;gt; $main_post_url,
        'post_type'      =&amp;gt; 'post',
        "posts_per_page" =&amp;gt; "-1",
        "order"          =&amp;gt; "ASC"
    )
);

//  Nothing to do if there are no inbound links
if ( $the_query-&amp;gt;have_posts() ) {
    //  Loop through the posts
    while ( $the_query-&amp;gt;have_posts() ) {
        //  Set up the query
        $the_query-&amp;gt;the_post();
        $post_id = get_the_ID();
        $title = esc_html( get_the_title() );
        $url   = get_the_permalink();

        //  Add the node
        add_item_to_nodes( $nodes, $post_id, $title, $url, "post" );
        //  Add the relationship
        add_relationship( $links, $post_id, $main_post_id );

        //  Get the tags of the Post
        $post_tags = get_the_tags( $post_id );

        //  Add the tags as nodes, and create links to main Post
        foreach($post_tags as $tag) {

            $id   = $tag-&amp;gt;term_id;
            $name = $tag-&amp;gt;name;

            //  Add the node
            add_item_to_nodes( $nodes, $id, $name, "https://shkspr.mobi/blog/tag/" . $name, "tag" );
            //  Add the relationship
            add_relationship( $links, $id, $post_id );
        }

    }
}

//  Get all the internal links from this post
//  Render the post as HTML
$content = apply_filters( "the_content", get_the_content( null, false, $ID ) );

//  Load it into HTML
$dom = new DOMDocument();
libxml_use_internal_errors( true );
$dom-&amp;gt;loadHTML( $content );
libxml_clear_errors();

//  Get any &amp;lt;a href="…" which starts with https://shkspr.mobi/blog/
$internal_links = [];
foreach ( $dom-&amp;gt;getElementsByTagName( "a" ) as $anchor ) {
    $href = $anchor-&amp;gt;getAttribute( "href" );
    if (preg_match('/^https:\/\/shkspr\.mobi\/blog\/\d{4}$/', $href)) {
        $internal_links[] = $href;
    }
}

//  Loop through the internal links, get their hashtags
foreach ( $internal_links as $url ) {
    $post_id = url_to_postid( $url );
    //  Get the Post's details
    $post_title = get_the_title( $id );

    //  Add the node
    add_item_to_nodes( $nodes, $post_id, $post_title, $url, "post" );
    //  Add the relationship
    add_relationship($links, $main_post_id, $post_id );

    //  Get the tags of the Post
    $post_tags = get_the_tags( $post_id );

    //  Add the tags as nodes, and create links to main Post
    foreach( $post_tags as $tag ) {
        $id   = $tag-&amp;gt;term_id;
        $name = $tag-&amp;gt;name;

        //  Add the node
        add_item_to_nodes( $nodes, $id, $name, "https://shkspr.mobi/blog/tag/" . $name, "tag" );
        //  Add the relationship
        add_relationship( $links, $id, $post_id );
    }
}

//  Deduplicate the nodes and links
$nodes_unique = array_unique( $nodes, SORT_REGULAR );
$links_unique = array_unique( $links, SORT_REGULAR );

//  Put them in the keyless format that D3 expects
$nodes_output = array();
$links_output = array();

foreach ( $nodes_unique as $node ) {
    $nodes_output[] = $node;
}

foreach ( $links_unique as $link ) {
    $links_output[] = $link;
}

//  Return the JSON
echo json_encode( $nodes_output, JSON_PRETTY_PRINT );
echo "\n";
echo json_encode( $links_output, JSON_PRETTY_PRINT );
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="creating-a-force-directed-svg"&gt;&lt;a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#creating-a-force-directed-svg"&gt;Creating a Force Directed SVG&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Once the data are spat out, you can include them in a web-page. Here's a basic example:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset="UTF-8"&amp;gt;
        &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
        &amp;lt;title&amp;gt;Force Directed Graph&amp;lt;/title&amp;gt;
        &amp;lt;script src="https://d3js.org/d3.v7.min.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;svg width="800" height="600"&amp;gt;
            &amp;lt;defs&amp;gt;
                &amp;lt;marker id="arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto" fill="#999"&amp;gt;
                &amp;lt;path d="M0,0 L10,3.5 L0,7 Z"&amp;gt;&amp;lt;/path&amp;gt;
                &amp;lt;/marker&amp;gt;
            &amp;lt;/defs&amp;gt;
        &amp;lt;/svg&amp;gt;
        &amp;lt;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class="language-js"&gt;            const nodes = [];
            const links = [];

            const width  = 800;
            const height = 600;

            const svg = d3.select("svg")
                .attr( "width",  width  )
                .attr( "height", height );

            const simulation = d3.forceSimulation( nodes )
                .force( "link",   d3.forceLink( links ).id( d =&amp;gt; d.id ).distance( 100 ) )
                .force( "charge", d3.forceManyBody().strength( -300 ) )
                .force( "center", d3.forceCenter( width / 2, height / 2 ) );

            //  Run simulation with simple animation
            simulation.on("tick", () =&amp;gt; {
                link
                    .attr("x1", d =&amp;gt; d.source.x)
                    .attr("y1", d =&amp;gt; d.source.y)
                    .attr("x2", d =&amp;gt; d.target.x)
                    .attr("y2", d =&amp;gt; d.target.y);   node
                    .attr("transform", d =&amp;gt; `translate(${d.x},${d.y})`);
            });

            // Draw links
            const link = svg.selectAll( ".link" )
                .data(links)
                .enter().append("line")
                .attr( "stroke", "#999" )
                .attr( "stroke-width", 2 )
                .attr( "x1", d =&amp;gt; d.source.x )
                .attr( "y1", d =&amp;gt; d.source.y )
                .attr( "x2", d =&amp;gt; d.target.x )
                .attr( "y2", d =&amp;gt; d.target.y )
                .attr( "marker-end", "url(#arrowhead)" );

            //  Draw nodes
            const node = svg.selectAll( ".node" )
                .data( nodes )
                .enter().append( "g" )
                .attr( "class", "node" )
                .attr( "transform", d =&amp;gt; `translate(${d.x},${d.y})` )
                .call(d3.drag() //  Make nodes draggable
                    .on( "start", dragStarted )
                    .on( "drag",  dragged )
                    .on( "end",   dragEnded ) 
                );

            //  Add hyperlink
            node.append("a")
            .attr( "xlink:href", d =&amp;gt; d.url ) //    Link to the node's URL
            .attr( "target", "_blank" ) //  Open in a new tab
            .each(function (d) {
                const a = d3.select(this);
                //  Different shapes for posts and tags
                if ( d.group === "post" ) {
                    a.append("circle")
                        .attr("r", 10)
                        .attr("fill", "blue");
                } else if ( d.group === "tag" ) {
                    //  White background rectangle
                    a.append("rect")
                            .attr("width", 20)
                            .attr("height", 20)
                            .attr("x", -10)
                            .attr("y", -10)
                            .attr("fill", "white"); 
                    // Red octothorpe
                    a.append("path")
                            .attr("d", "M-10,-5 H10 M-10,5 H10 M-5,-10 V10 M5,-10 V10") 
                            .attr("stroke", "red")
                            .attr("stroke-width", 2)
                            .attr("fill", "none");
                }
                //  Text label
                a.append( "text")
                    .attr( "dy", 4 )
                    .attr( "x", d =&amp;gt; ( d.group === "post" ? 12 : 14 ) )
                    .attr( "fill", "black" )
                    .style("font-size", "12px" )
                    .text( d.label );
            });

            //  Standard helper functions to make nodes draggable
            function dragStarted( event, d ) {
                if ( !event.active ) simulation.alphaTarget(0.3).restart();
                d.fx = d.x;
                d.fy = d.y;
            }
            function dragged( event, d ) {
                d.fx = event.x;
                d.fy = event.y;
            }
            function dragEnded( event, d ) {
                if (!event.active) simulation.alphaTarget(0);
                d.fx = null;
                d.fy = null;
            }
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class="language-html"&gt;        &amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="next-steps"&gt;&lt;a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#next-steps"&gt;Next Steps&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;It needs a bit of cleaning up if I want to turn it into a WordPress plugin. It might be nice to make it a static SVG rather than relying on JavaScript. And the general æsthetic needs a bit of work.&lt;/p&gt;

&lt;p&gt;Perhaps I could make it 3D like my &lt;a href="https://shkspr.mobi/blog/2023/04/msc-dissertation-exploring-the-visualisation-of-hierarchical-cybersecurity-data-within-the-metaverse/"&gt;MSc Dissertation&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;But I'm pretty happy with that for an afternoon hack!&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://gitlab.com/edent/blog-theme/-/blob/master/includes/graph.php"&gt;get the code&lt;/a&gt; if you want to play.&lt;/p&gt;
&lt;img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=55159&amp;amp;HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager"&gt;</content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Surrey Police and the Case of The Misleading Pie Charts]]></title>
		<link>https://shkspr.mobi/blog/2013/03/surrey-police-and-the-case-of-the-misleading-pie-charts/</link>
					<comments>https://shkspr.mobi/blog/2013/03/surrey-police-and-the-case-of-the-misleading-pie-charts/#respond</comments>
				<dc:creator><![CDATA[Terence Eden]]></dc:creator>
		<pubDate>Mon, 18 Mar 2013 07:30:10 +0000</pubDate>
		<category><![CDATA[data]]></category><category><![CDATA[graphs]]></category><category><![CDATA[police]]></category><category><![CDATA[statistics]]></category><category><![CDATA[stats]]></category><category><![CDATA[surrey]]></category>		<guid isPermaLink="false">http://shkspr.mobi/blog/?p=7820</guid>

					<description><![CDATA[Surrey County Council have sent every household in the county a booklet explaining how our council tax is being spent.  Within it is a highly political comment from Kevin Hurley, the newly elected Police and Crime Commissioner.  He presents a pie chart showing how the police force spend its money.  Take a look at it and ask yourself this question: what percentage is spent on &#34;Employees&#34;.   …]]></description>
										<content:encoded>&lt;p&gt;Surrey County Council have sent every household in the county a booklet explaining how our council tax is being spent.  Within it is a highly political comment from Kevin Hurley, the newly elected Police and Crime Commissioner.&lt;/p&gt;

&lt;p&gt;He presents a pie chart showing how the police force spend its money.  Take a look at it and ask yourself this question: what percentage is spent on "Employees".&lt;/p&gt;

&lt;img src="https://shkspr.mobi/blog/wp-content/uploads/2013/03/Surrey-Police-Pie-Chart1.jpg" alt="Surrey Police Pie Chart" width="600" height="363" class="aligncenter size-full wp-image-7817"&gt;

&lt;p&gt;&lt;del datetime="2025-01-23T06:42:40+00:00"&gt;Please use this poll to record your guess - answers at the end of this blog.&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;Pie charts have a long and noble history.  They were popularised by Florence Nightingale and were hugely effective in helping politicians understand the causes of death among soldiers during the Crimean War.
&lt;a title="By w:Florence Nightingale (1820–1910). (http://www.royal.gov.uk/output/Page3943.asp) [Public domain], via Wikimedia Commons" href="http://commons.wikimedia.org/wiki/File%3ANightingale-mortality.jpg"&gt;&lt;img width="512" alt="Nightingale-mortality" src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Nightingale-mortality.jpg/960px-Nightingale-mortality.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we understand more about the human brain and how we perceive shapes, it is becoming clear that &lt;a href="https://en.wikipedia.org/wiki/Pie_chart#Use_and_effectiveness"&gt;pie charts are ineffective for representing complex information&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;2D pie charts can still serve a useful purpose in limited circumstances.  The real problem is with &lt;em&gt;3D&lt;/em&gt; pie charts.  As far as I can tell, these abominations were popularised by Microsoft's Excel charting software.&lt;/p&gt;

&lt;p&gt;3D charts distort the view of the data in such a way that it becomes increasingly hard to understand the information being presented.  A picture being worth 1000 words, allow me to demonstrate:
&lt;img src="https://shkspr.mobi/blog/wp-content/uploads/2013/03/GraphJam3d.jpg" alt="GraphJam3d" width="499" height="335" class="aligncenter size-full wp-image-7818"&gt;&lt;/p&gt;

&lt;p&gt;So, just how bad is Surrey Police's Pie Chart?  In an &lt;em&gt;extremely&lt;/em&gt; scientific study of asking half a dozen people, they all guessed between 75% and 85%.  That's quite a wide range considering it's a multi-million pound difference.&lt;/p&gt;

&lt;p&gt;On the opposite page to the pie chart is this summary of spending.
&lt;img src="https://shkspr.mobi/blog/wp-content/uploads/2013/03/Police-Spending.jpg" alt="Police Spending" width="600" height="320" class="aligncenter size-full wp-image-7816"&gt;&lt;/p&gt;

&lt;p&gt;In slightly more readable format, it is:&lt;/p&gt;

&lt;table&gt;
    &lt;tbody&gt;&lt;tr&gt;
        &lt;td&gt;&lt;b&gt;Category&lt;/b&gt;&lt;/td&gt;
        &lt;td&gt;&lt;b&gt;£&lt;/b&gt;&lt;/td&gt;
        &lt;td&gt;&lt;b&gt;%&lt;/b&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;b&gt;Employees&lt;/b&gt;&lt;/td&gt;
        &lt;td&gt;£181.70&lt;/td&gt;
        &lt;td&gt;81.9%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;b&gt;Premises&lt;/b&gt;&lt;/td&gt;
        &lt;td&gt;£8.00&lt;/td&gt;
        &lt;td&gt;3.6%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;b&gt;Supplies&lt;/b&gt;&lt;/td&gt;
        &lt;td&gt;£27.20&lt;/td&gt;
        &lt;td&gt;12.3%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;b&gt;Transport&lt;/b&gt;&lt;/td&gt;
        &lt;td&gt;£5.00&lt;/td&gt;
        &lt;td&gt;2.3%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;b&gt;Total&lt;/b&gt;&lt;/td&gt;
        &lt;td&gt;£216.90&lt;/td&gt;
        &lt;td&gt;97.7%&lt;/td&gt;
    &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;&lt;br&gt;
A few interesting things to note here.&lt;/p&gt;

&lt;p&gt;Firstly, how do we calculate the percentages?  The total spend isn't mentioned in the report (£216.90).  If we use that, "Employees" accounts for 81.9% of spending.&lt;/p&gt;

&lt;p&gt;If we take into account the gross expenditure (£207.70) the figure jumps to &lt;strong&gt;87.5%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Secondly, if we do assume that we're using the unreported total spend - there is at least 2% missing.  Some of which can be explained by rounding - but I wonder what the rest of the money is spent on.&lt;/p&gt;

&lt;p&gt;Given the above, I don't think the provided pie chart allows Surrey residents to see an accurate view of how their hard earned money is being spent.&lt;/p&gt;

&lt;p&gt;Hopefully, this side-by-side - of the above data - will show you how 3D pie charts distort data and end up misleading their audience.
&lt;img src="https://shkspr.mobi/blog/wp-content/uploads/2013/03/3d-2d-pie-chart-side-by-side.jpg" alt="3d 2d pie chart side by side" width="600" height="266" class="aligncenter size-full wp-image-7827"&gt;&lt;/p&gt;

&lt;p&gt;With this overlay, we can see the distortion much more clearly.  The smaller sections of the chart look disproportionately larger.
&lt;img src="https://shkspr.mobi/blog/wp-content/uploads/2013/03/Pie-Charts-Overlayed.png" alt="Pie Charts Overlayed" width="408" height="377" class="aligncenter size-full wp-image-7828"&gt;
It's time to announce a zero tolerance crackdown on dodgy data representation.&lt;/p&gt;
&lt;img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=7820&amp;amp;HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager"&gt;</content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2013/03/surrey-police-and-the-case-of-the-misleading-pie-charts/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
