Tinted Tags

Overview

How to create a tag cloud in Rails that uses colour to indicate the popularity of a tag. Uses compass and acts-as-taggable-on gems.

Tag Clouds

The purpose of the tag cloud is to highlight the most popular tags by using font size to represent tag usage. Whereas this is a useful tool for the user, if you care about good typography and keeping to the grid you probably won’t want to include one of these janky things in your sidebar.

tag cloud

What if there were another way to represent the popularity of each tag? Made By Many have taken an interesting approach in using colour to indicate tag popularity.

made by many tag cloud

Here’s one way to create tinted tags..

Acts As Taggable On

acts-as-taggable-on is the obvious choice for adding tagging functionality to Rails.

First make your model taggable by following the instructions in the gem readme.

The gem creates two ActiveRecord models; Tag and Tagging, the latter is an association model.

We need to add a new attribute, :tint to the Tag model. This is where we will save a generated hex code that represents the popularity of the tag.

1
rails generate migration add_tint_to_tags tint:string
20130130161730_add_tint_to_tags.rb
1
2
3
4
5
class AddTintToTags < ActiveRecord::Migration
  def change
    add_column :tags, :tint, :string
  end
end

TagTinter

First we need a way to calculate colours based on percentages.

lib/tag_tinter.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'compass'

class TagTinter

  ...

  #
  # evaluate("mix(#ffffff, #000000, 50%)") => "#7f7f7f"
  #
  def evaluate(value)
    Sass::Script::Parser.parse(value, 0, 0).perform(Sass::Environment.new).to_s
  end

  ...

end

Now we need a way to calculate tag usage as a percentage.

lib/tag_tinter.rb
1
2
3
4
5
6
7
8
9
10
11
12
class TagTinter

  ...

  def calculate_percentage(count)
    total = Post.tag_counts_on(:tags).map(&:count).inject(:+)
    ((count * 100) / total).round(2)
  end

  ...

end

Calculate tag subtotals.

lib/tag_tinter.rb
1
2
3
4
5
6
7
8
9
10
11
class TagTinter

  ...

  Post.tag_counts_on(:tags).each do |tag|
    percent = calculate_percentage(tag.count)
  end

  ...

end

All together.

lib/tag_tinter.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
require 'compass'

class TagTinter
  def initialize(base, tint)
    @base, @tint = base, tint
  end

  def update_tints
    Post.tag_counts_on(:tags).each do |tag|
      percent = calculate_percentage(tag.count)
      tag.tint = evaluate("mix(#{@base}, #{@tint}, #{percent})")
      tag.save
    end
  end

  private

  def calculate_percentage(count)
    total = Post.tag_counts_on(:tags).map(&:count).inject(:+)
    ((count * 100) / total).round(2)
  end

  def evaluate(value)
    Sass::Script::Parser.parse(value, 0, 0).perform(Sass::Environment.new).to_s
  end
end

after_save Filter

Depending on how many taggings you have, this is a potentially expensive operation. One option would be to use a task scheduler such as delayed_job or whenever to recalculate tag tints. I chose to use an after_save callback..

post.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
require 'tag_tinter'

class Post < ActiveRecord::Base
  attr_accessible :title, :tag_list
  acts_as_taggable
  after_save :update_tints, if: :tag_list_changed?

  private

  def update_tints
    TagTinter.new('black', 'white').update_tints
  end
end

Displaying the Tag Cloud

Grab all the tags from the database.

posts_controller.rb
1
2
3
4
5
class PostsController < ApplicationController
  def index
    @tags = Post.tag_counts_on(:tags).order('count desc')
  end
end

Add the each tag’s colour to its html element via inline-css.

posts/index.erb
1
2
3
4
5
6
7
<ul>
  <% @tags.each do |tag| %>
    <li style="background-color: <%= tag.tint %>; display: inline-block;">
      <%= tag.name %>
    </li>
  <% end %>
</ul>

Route Globbing

Using route globbing tags can be passed as parameters to a search via the url.

routes.rb
1
2
3
4
5
YourApp::Application.routes.draw do

  match '/blog/tags/*tag' => "posts#by_tag", :as => 'posts_by_tag'

end
1
2
3
4
5
6
7
8
9
class PostsController < ApplicationController
  ...

  def by_tag
    @posts = Post.tagged_with(params[:tag]).order('created_at desc')
  end

  ...
end

Now we can do this..

posts/index.erb
1
2
3
4
5
6
7
<ul>
  <% @tags.each do |tag| %>
    <li style="background-color: <%= tag.tint %>; display: inline-block;">
      <%= link_to tag.name, posts_by_tag_path(tag.name) %>
    </li>
  <% end %>
</ul>
posts/by_tag.erb
1
2
3
4
5
6
<h2> Tag: <%= params[:tag] %> </h2>
<h3> <%= @posts.count %> posts </h3>

<% unless @posts.blank? %>
  <%= render @posts %>
<% end %>

Done!

Tinted Tags Gem

For an easier way to use this code, here is a gem - tinted_tags

Comments