Issue Count CI Coverage Status Gem Version Gem Downloads

Lab42::TagCloud

Creating Tag Clouds with gamma correct color values and styles from a simple DSL.

This is a port of Elixir's tag_cloud to Ruby

Installation:

With bundler
  gem 'lab42_tag_cloud'
or simply
    gem install lab42_tag_cloud

Usage:

A very simple DSL allows you to describe the size, color and thickness of your tag and each line of this DSL will be transformed into some CSS and the text of the tag.

Colors

Gamma correction for scaled colors

To create 13 different shades of a color, where 0 means transparent (#ffffff) and 12 opaque (original color value or #000000 as default) which are equally spaced for the human eye we use a gamma correction of 1/2.2 which seems to work very well on modern screens.

The result for all 13 shades for some colors can be seen here

Right now the size of the scale and the gamma value cannot be modified but that could be easily implemented if desired.

Well let us describe the behavior of this DSL by means of speculations.

Context: The DSL

The DSL is a very simple string with 3 white space separated words, of which the first is a color specification which we speculate about below, the second a font size in CSS (defaulting to pt if only a number is specified) and the third the font weight as an optional integer with defaults to 100

This simply is translated to HTML style attributes as follows:

Given the following DSL strings

    include Lab42::TagCloud

     let(:blue_bold) { "blue 10 800" }
     let(:dgr_shaded) { "10/darkgoldenrod 1.2em" }
     let(:explicit) { "6/#2f3ab0 30px 450" }

Then the to_style method will yield the following results

    expect(to_style(blue_bold)).to eq("color: #0000ff; font-size: 10pt; font-weight: 800;")
    expect(to_style(dgr_shaded)).to eq("color: #995061; font-size: 1.2em;")
    expect(to_style(explicit)).to eq("color: #695676; font-size: 30px; font-weight: 450;")

Context: Colors

Given we include the module:

    include Lab42::TagCloud

Then we can see some transformation for colors, e.g. shade 11 of black can be expressed in different ways

    gray = "525252"
    expect(color_value(11)).to eq(gray)
    expect(color_value("11")).to eq(gray)
    expect(color_value("11/black")).to eq(gray)
    expect(color_value("11/#000000")).to eq(gray)

And we can also use all web color names

    expect(color_value("10/blue")).to eq("7171ff")
    expect(color_value("10/lime")).to eq("71ff71")

And we can add underscores for readability to the color names

    expect(color_value("4/medium_slate_blue")).to eq("0d16e0")

Context: Convenience Helpers

Now we have already everything we need to create nice tag clouds, e.g. with ERB, however with the API described so far we would need to write code like the following:

      <% somd_data_source.each do |datum| %>
         ...
         <span style="<%= Lab42::TagCloud.to_style(datum.dsl) %>"><%= datum.tag %></span>

However if we had, say a helper, that would operate on objects that respond to dsl and tag, or [:dsl] and [:tag], then we could do very nice things like

    <%= some_data_source.map { Lab42::TagCloud.tag_from_object(_1, tag: "span") } %>

would that not be great?

Well guess what, it is great, and the helper is called tag_from_object indeed

Given an OpenStruct and a Hash instance of the required format

    let(:ostruct) { OpenStruct.new(tag: "Ruby", dsl: "10/red 1.2em") }
    let(:hash) { {tag: "Elixir", dsl: "blue 1.5em 800"} }
    let(:elixir_style) { %{ style="color: #0000ff; font-size: 1.5em; font-weight: 800;"} }
    let(:ruby_style) { %{ style="color: #ff7171; font-size: 1.2em;"} }
    let(:two) { [ostruct, hash] }

Then we can obtain tags from these objects

    expect(tag_from_object(ostruct)).to eq(%{<span#{ruby_style}>Ruby</span>})
    expect(tag_from_object(hash, tag: :div, class: "some-class"))
      .to eq(%{<div class="some-class"#{elixir_style}>Elixir</div>})

And we can map them together

    expected =
      %{<span#{ruby_style}>Ruby</span>&nbsp;<span#{elixir_style}>Elixir</span>}

    expect(two.map {tag_from_object(_1)}.join("&nbsp;"))
      .to eq(expected)

But that is cumbersome and we want a more flexible approach: Enter tags_from_collction

Then with this we can do things like:

    expected =
    %{<li#{ruby_style}><i>Ruby</i></li>&nbsp;-&nbsp;<li#{elixir_style}><i>Elixir</i></li>}

    expect(tags_from_collection(two, tag: :li, before: "<i>", after: "</i>", join: "&nbsp;-&nbsp;"))
      .to eq(expected)

Typically such tag clouds can than be easily constructed from external data sources like JSON or YAML

LICENSE

Copyright 2022 Robert Dober [email protected]

Apache-2.0 c.f LICENSE