module Carto class LegendMigrator attr_reader :layer_id, :legend def initialize(layer_id, legend) @layer_id = layer_id @legend = legend end def build Legend.new(layer_id: layer_id, title: title.present? && legend['show_title'] ? title : nil, type: 'custom', definition: definition) rescue => exception CartoDB::Logger.debug(message: 'Carto::LegendMigrator: couldn\'t migrate', exception: exception, legend: @legend, layer_id: @layer_id) Legend.new(layer_id: @layer_id) end private def type @type ||= legend['type'] end def items @items ||= legend['items'] end def title legend['title'] end def template @template ||= legend['template'] end HTML_RAMP_TYPES = %w(choropleth intensity density).freeze def definition if template.present? { html: template } elsif type == 'custom' build_custom_definition_from_custom_type elsif type == 'category' build_custom_definition_from_custom_type elsif type == 'bubble' build_custom_definition_from_bubble elsif HTML_RAMP_TYPES.include?(type) build_custom_definition_from_ramp_type end end COLOR_REGEXP = /^#(?:[0-9a-fA-F]{3}){1,2}$/ CSS_URL_REGEX = /^(?:url\(['"]?)(.*?)(?:['"]?\))$/ STATIC_ASSETS_REGEX = /http:\/\/com.cartodb.users-assets.production.s3.amazonaws.com(.*)/ def build_custom_definition_from_custom_type categories = items.each_with_index.map do |item, index| title = item['name'].to_s || "Category #{index + 1}" value = item['value'] category_definition = { title: title } if value css_url_match = CSS_URL_REGEX.match(value) if css_url_match category_definition[:icon] = update_static_assets_path(css_url_match[1]) elsif value =~ COLOR_REGEXP category_definition[:color] = value else category_definition[:icon] = update_static_assets_path(value) end end category_definition end { categories: categories } end def update_static_assets_path(value) static_assets_match = STATIC_ASSETS_REGEX.match(value) if static_assets_match value = "https://s3.amazonaws.com/com.cartodb.users-assets.production#{static_assets_match[1]}" end value end def build_custom_definition_from_ramp_type left_label, right_label = labels_for_items style = style_for_gradient html = %(
\n) html += %(

#{left_label}

\n) html += %(

#{right_label}

\n) html += %(
\n) html += %(
\n) { html: html } end def style_for_gradient first_color_index = labels_for_items.compact.count item_colors = items[first_color_index..-1].map do |item| color = item['value'].downcase color if color =~ COLOR_REGEXP end compact_item_colors = item_colors.compact if compact_item_colors.count == 1 compact_item_colors << generate_end_color(item_colors.first) end gradient_stops = compact_item_colors.join(', ') "background: linear-gradient(90deg, #{gradient_stops})" end def build_custom_definition_from_bubble(steps: 6) left, right = labels_for_items heights, values = heights_and_values(left, right, steps) html = %(
\n) html += %( \n) html += %(
\n) html += %( \n) html += %(
\n) html += %(
\n) { html: html } end def heights_and_values(min, max, steps) if min.is_a?(Numeric) && max.is_a?(Numeric) step = (max - min) / (steps - 1) values = Array.new(steps) do |index| formatted_string_number(min + (index * step)) end else values = [min] + [""] * (steps - 2) + [max] end heights = Array.new(steps) do |index| 100 - (index * 100 / (steps - 1)) end [heights, values] end def labels_for_items return @labels_for_items if @labels_for_items first_item = items.first second_item = items.second if first_item && first_item['type'] == 'text' left_label = first_item['value'] end if second_item && second_item['type'] == 'text' right_label = second_item['value'] end @labels_for_items = [left_label, right_label] @labels_for_items end def generate_end_color(start_color, brighten_steps: 4) start_red, start_green, start_blue = html_color_to_rgb(start_color) brightened_red = start_red brightened_green = start_green brightened_blue = start_blue brighten_steps.times do brightened_red = (brightened_red * start_red / 255) brightened_green = (brightened_green * start_green / 255) brightened_blue = (brightened_blue * start_blue / 255) end hex_red = brightened_red.to_s(16) hex_green = brightened_green.to_s(16) hex_blue = brightened_blue.to_s(16) if hex_red.length == 1 hex_red = [hex_red, hex_red].join end if hex_green.length == 1 hex_green = [hex_green, hex_green].join end if hex_blue.length == 1 hex_blue = [hex_blue, hex_blue].join end "##{hex_red}#{hex_green}#{hex_blue}" end def html_color_to_rgb(html_color) stripped = html_color.delete('#') if stripped.length == 3 characters = stripped.map { |character| [character, character] } stripped = characters.flatten.join end [stripped[0..1].hex, stripped[2..3].hex, stripped[4..5].hex] end def formatted_string_number(number) if number < 1_000 pretty_round(number).to_s elsif number < 1_000_000 "#{pretty_round(number / 1_000)}K" else "#{pretty_round(number / 1_000_000)}M" end end def pretty_round(number) integer = number.to_i rounded = number.round(1) integer == rounded ? integer : rounded end end end