Tag cloud sorting

chombee <[email protected]> Thu, 01 Nov 2007 17:24:21 +0000
Newsgroups gmane.comp.web.pyblosxom.user
Message-ID <1193937861.5639.37.camel@s0094060-desktop>
Attached is a modified version of the tagcloud plugin. I couldn't get
folksonomy to work so I'm using tags and tagcloud instead. Anyway, the
tagcloud plugin outputs tags in a random order, which is not very
useful. My version adds two new template variables:

$alphabetical_tagcloud -- output tags sorted alphabetically
$popularity_tagcloud -- output tags sorted by popularity.

It also moves the "untagged" tag onto a line of its own at the bottom of
the tag cloud. I don't think it makes sense to have "untagged" mixed in
with all the other tags.

This image shows the same tagcloud unsorted, sorted alphabetically, and
sorted by popularity:

http://www.23hq.com/seanh/photo/2562773/original

Personally I think that by popularity looks cleanest, although
alphabetical may be more useful if you're scanning for a particular tag.
They have their pros and cons.

The implementation is a bit ugly right now in two ways:

Sorting tags by popularity added some code duplication. The whole file
could do with some refactoring to handle this sort of option well. (You
could add more options, to output tags as a list instead of a cloud, for
example).

It creates all four kinds of tag cloud every time the plugin runs, and
stores each one in a different template variable. I don't really know
how pyblosxom plugins work, I'm just hacking the existing ones that I
use. Is there some way for a template to pass a parameter to the plugin
telling it what kind of tag cloud to create? I recall seeing the syntax
for something like this being discussed but I don't remember if it was
an existing feature or a proposed one.

Thanks

-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >> http://get.splunk.com/

_______________________________________________
pyblosxom-users mailing list
pyblosxom-users-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
https://lists.sourceforge.net/lists/listinfo/pyblosxom-users
tag_cloud.py (text/x-python, 7.8 KB)
"""
tag_cloud.py


* * *
chombee 2007-11-01 13:26:59

Added ability to sort tag clouds either alphabetically or by popularity. Adds two new template variables for sorted tag clouds. It's a bit ugly right now:

* Sorting tags by popularity added some code duplication. The whole file could do with some refactoring to handle this option well.
* It creates all four kinds of tag cloud every time the plugin runs. Is there some way for a template to pass a parameter to the plugin telling what kind of tag cloud to create?

Also moved the "untagged" tag to a line on its own at the bottom of the tag cloud.
* * *

Creates a tagcloud to compliment the tags plugin. Simply copy to your plugins directory and
enable in py['load_plugins'] if necessary. The tags plugin must be installed and properly 
configured.

Simply add a #tags tag1[,tag2[...]] to your entry metadata section.

You should then define the following classes:
    .smallestTag
    .smallTag
    .mediumTag
    .bigTag
    .biggestTag
    .hugeTag
    .hugestTag
    .mostHugeTag

as well as

    #tagcloud

The <size>Tag classes will be used on <a /> elements to represent the number of entries with 
that tag.

in your CSS to fit your needs.

As of 1.3.1, tag_cloud supports the following config property:
    py[ 'ignore_tags' ] = [ ]	- Use to omit tags from the tagcloud.
    py[ 'tag_url_display' ] = [ ]   - Useful if you use mod_rewrite to redirect your tags url. Defaults to config['tag_url']

where 'ignore_tags' is a list of tags to ignore while generating the tag cloud. This way, if you have a 
very widely used tag, such as "general", which is overwhelming your tag and isn't necessary, you can choose 
to omit it. 

As of 1.0.2, tag_cloud obeys the ignore_directories propery in pyblosxom.

Version 1.0.3 - Fixed an error choosing tag size; never used smallestTag

Version 1.1.0 - Fixed an error choosing tag size; was not properly choosing mincount. Also added
                finer breakdown of distribution, better for smaller sites.

Version 1.2.0 - Added an "untagged" tag to the cloud after Joe accepted my untagged hack to his tags plugin.
                Make sure you're using Tags v 2 2005/10/23 1:30:00 or later!!
                NOTE: The "untagged" tag does not factor into the tag weightings, and will always be 
                    shown using a .mediumTag class.

Version 1.3.0 - Added a "populartagcloud" variable, which takes the full "tagcloud", 
                removes anything which is not at least a "medium tag", and redistributes 
                the weightings. This is great if you have a lot of tags with only 
                one or two posts that don't pertain to the main content of your blog.

Version 1.3.1 - Added defaults for config[] parameters that weren't checked for
                Fixed bug where mincount was always 1 instead of the actual mincount
"""

__author__ = 'Timothy C. Fanelli <[email protected]>'
__version__ = '1.3.1'
__url__ = 'http://www.timfanelli.com'

# Variables

import os, re, sys, string

def cb_prepare(args):
    request = args['request']
    config  = request.getConfiguration()
    data    = request.getData()

    maxcount = 1
    tagcount = {}

    ignoretags = []
    if config.has_key('ignore_tags'):
        config['ignore_tags']

    ignoredirectories = config[ 'ignore_directories' ]

    for root,dirs,files in os.walk( config['datadir'] ):
        for file in files:
            if not re.compile('.*\.txt$').search(file):
                continue
            
            entry_location = root + "/" + file

            directory = os.path.dirname(entry_location)
            if ( os.path.split( directory )[1] in ignoredirectories ):
                continue

            contents = open(entry_location,'r').read()

            m = re.compile( '\n#tags\s*(.*)\n' ).search(contents)
            if m:
                tagstring = m.group(1)
                tags = tagstring.split(',')

                first = True
                for tag in tags:
                    if ( tag in ignoretags ):
                        continue

                    count = 1
                    if tag in tagcount.keys():
                        count = tagcount[tag] + 1

                    tagcount[tag] = count
                    maxcount = max( count, maxcount )
            else:
                untaggedcount = 1
                if "untagged" in tagcount.keys():
                    untaggedcount = tagcount["untagged"] + 1

                tagcount["untagged"] = untaggedcount

    mincount = maxcount
    for tag in tagcount.keys():
        mincount = min( mincount, tagcount[tag] )

    data["tagcloud"] = createTagCloud( config, tagcount, mincount, maxcount )
    data["populartagcloud"] = createPopularTagCloud( config, tagcount, mincount, maxcount )
    data["alphabetical_tagcloud"] = createTagCloud( config, tagcount, mincount, maxcount, 'alphabetical' )
    data["popularity_tagcloud"] = createTagCloud( config, tagcount, mincount, maxcount, 'popularity' )
    
def createPopularTagCloud( config, tagcount, mincount, maxcount, sort=None ):
    distribution = ( maxcount - mincount ) / 6
    popcount = {}
    popmin = 999999

    for tag in tagcount.keys():
        count = tagcount[tag]
        if ( count > ( mincount + distribution ) ):
            popcount[tag] = count
            popmin = min( popmin, count )
    
    return createTagCloud( config, popcount, popmin, maxcount, sort )			

def case_insensitive_cmp(a, b):
    return cmp(a.upper(), b.upper())

def popularity_cmp(a, b):
    return cmp(a[1],b[1])

def createTagCloud( config, tagcount, mincount, maxcount, sort=None ):
    if tagcount:
        tagurl = config['tag_url']
        if config.has_key('tag_url_display'):
            tagurl = config['tag_url_display']

        tagcloud = []
        tagcloud.append("<div id='tagcloud'>")
        distribution = ( maxcount - mincount ) / 6

        sortedtags = tagcount.keys()
        if sort == 'alphabetical':
            sortedtags.sort(case_insensitive_cmp)
        elif sort == 'popularity':
            items = tagcount.items()
            items.sort(popularity_cmp,reverse=True)
            sortedtags = []
            for item in items:
                sortedtags.append(item[0])

        untagged = False # Does the 'untagged' tag exist?
        for tag in sortedtags:
            size = "mediumTag"

            if tag == "untagged":
                untagged = True
            else:
                if ( (int)(tagcount[tag]) == maxcount ):
                    size = "mostHugeTag"
                elif ( (int)(tagcount[tag]) > ( mincount + ( distribution * 5 ) ) ):
                    size = "hugestTag"
                elif ( (int)(tagcount[tag]) > ( mincount + ( distribution * 4 ) ) ):
                    size = "hugeTag"
                elif ( (int)(tagcount[tag]) > ( mincount + ( distribution * 3 ) ) ):
                    size = "biggestTag"
                elif ( (int)(tagcount[tag]) > ( mincount + ( distribution * 2 ) ) ):
                    size = "bigTag"
                elif ( (int)(tagcount[tag]) > ( mincount + distribution ) ):
                    size = "mediumTag"
                elif ( (int)(tagcount[tag]) > mincount ):
                    size = "smallTag"
                elif ( (int)(tagcount[tag]) == mincount ):
                    size = "smallestTag"

                tagcloud.append( "<a href='%s' class='%s' alt='There are %s entries tagged %s'>%s</a>\n" % ( '%s%s' % ( tagurl,tag ), size, str(tagcount[tag]), tag, tag  ) )
        
        if untagged:
            tagcloud.append("<br/>")
            tag = "untagged"
            size = "mediumTag"
            tagcloud.append( "<a href='%s' class='%s' alt='There are %s entries tagged %s'>%s</a>\n" % ( '%s%s' % ( tagurl,tag ), size, str(tagcount[tag]), tag, tag  ) )
                                
        tagcloud.append("</div>")
        result = "".join(tagcloud)
        return result