Skip to content

Instantly share code, notes, and snippets.

@yeban
Created April 3, 2012 07:36
Show Gist options
  • Star 34 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save yeban/2290195 to your computer and use it in GitHub Desktop.
Save yeban/2290195 to your computer and use it in GitHub Desktop.
Flexible, logarithmic distribution, tag cloud for Jekyll.
# Copyright (C) 2011 Anurag Priyam - MIT License
module Jekyll
# Jekyll plugin to generate tag clouds.
#
# The plugin defines a `tag_cloud` tag that is rendered by Liquid into a tag
# cloud:
#
# <div class='cloud'>
# {% tag_cloud %}
# </div>
#
# The tag cloud itself is a collection of anchor tags, styled dynamically
# with the `font-size` CSS property. The range of values, and unit to use for
# `font-size` can be specified with a very simple syntax:
#
# {% tag_cloud font-size: 16 - 28px %}
#
# The output is automatically formatted to use the same number of decimal
# places as used in the argument:
#
# {% tag_cloud font-size: 0.8 - 1.8em %} # => 1
# {% tag_cloud font-size: 0.80 - 1.80em %} # => 2
#
# Tags that have been used less than a certain number of times can be
# filtered out from the tag cloud with the optional `threshold` parameter:
#
# {% tag_cloud threshold: 2%}
#
# Both the parameters can be easily clubbed together:
#
# {% tag_cloud font-size: 50 - 150%, threshold: 2%}
#
# The plugin randomizes the order of tags every time the cloud is generated.
class TagCloud < Liquid::Tag
safe = true
# tag cloud variables - these are setup in `initialize`
attr_reader :size_min, :size_max, :precision, :unit, :threshold
def initialize(name, params, tokens)
# initialize default values
@size_min, @size_max, @precision, @unit = 70, 170, 0, '%'
@threshold = 1
# process parameters
@params = Hash[*params.split(/(?:: *)|(?:, *)/)]
process_font_size(@params['font-size'])
process_threshold(@params['threshold'])
super
end
def render(context)
# get an Array of [tag name, tag count] pairs
count = context.registers[:site].tags.map do |name, posts|
[name, posts.count] if posts.count >= threshold
end
# clear nils if any
count.compact!
# get the minimum, and maximum tag count
min, max = count.map(&:last).minmax
# map: [[tag name, tag count]] -> [[tag name, tag weight]]
weight = count.map do |name, count|
# logarithmic distribution
weight = (Math.log(count) - Math.log(min))/(Math.log(max) - Math.log(min))
[name, weight]
end
# shuffle the [tag name, tag weight] pairs
weight.sort_by! { rand }
# reduce the Array of [tag name, tag weight] pairs to HTML
weight.reduce("") do |html, tag|
name, weight = tag
size = size_min + ((size_max - size_min) * weight).to_f
size = sprintf("%.#{@precision}f", size)
html << "<a style='font-size: #{size}#{unit}' href='/tags.html##{name}'>#{name}</a>\n"
end
end
private
def process_font_size(param)
/(\d*\.{0,1}(\d*)) *- *(\d*\.{0,1}(\d*)) *(%|em|px)/.match(param) do |m|
@size_min = m[1].to_f
@size_max = m[3].to_f
@precision = [m[2].size, m[4].size].max
@unit = m[5]
end
end
def process_threshold(param)
/\d*/.match(param) do |m|
@threshold = m[0].to_i
end
end
end
end
Liquid::Template.register_tag('tag_cloud', Jekyll::TagCloud)
@kendru
Copy link

kendru commented Oct 29, 2012

Thanks for this! This works great for the tag cloud on my jekyll-based blog.

@MartinThoma
Copy link

Thanks!

Could you please add an option that allows me to sort the tags alphabetically? I think it's disturbing when the site always looks different.

By now, I've commented weight.sort_by! { rand } out. But what do I have to change if I want to sort by tag name?

@MartinThoma
Copy link

I've replaced line 82 by

html << "<a style='font-size: #{size}#{unit}' href='" << Jekyll.configuration({})['baseurl'] << "/tag/#{slug}' title='#{count} topics'>#{name}</a>\n"

This gives absolutee links to tag pages. But it also shows Configuration file: /home/moose/Downloads/MartinThoma.github.io/_config.yml very often. I guess the tag cloud is generated every time a new page is generated. This is hopelessly inefficient.

How could we create the tag cloud once and only insert the generated HTML where needed?

@cboettig
Copy link

Break this into two plugins: a generator plugin instead and a filter plugin (See the Jekyll documentation on writing plugins). The generator will run first and only once, construct a hash containing all the data you need to make the tag cloud (e.g name, weight pairs). Then you can define filter tag as you do above, but just have it read the hash from the generator rather than constructing the data.

@acdcjunior
Copy link

For sorting the tags alphabetically, switching: weight.sort_by! { rand } for:
weight.sort_by! { |m| m.first.downcase } worked for me. But I really know nothing about ruby so...

@blackgirl
Copy link

What is i have to write in _config.yml file to start it? Just my second day with Jekyll so i full of love as well as chaos in fact))
P.S. Nice nickname :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment