Skip to content

Instantly share code, notes, and snippets.

@simon-engledew
Created January 27, 2023 09:21
Show Gist options
  • Save simon-engledew/6dfab9a14c98d5e34caf7d914d7cde2a to your computer and use it in GitHub Desktop.
Save simon-engledew/6dfab9a14c98d5e34caf7d914d7cde2a to your computer and use it in GitHub Desktop.
Donut Graph
<div class="d-inline-block" style="height:<%= size %>px;width:<%= size %>px;position:relative">
<svg aria-label="<%= value %> / <%= total %>" viewBox="0 0 100 100" height="<%= size %>" width="<%= size %>">
<path fill="var(--color-<%= donut_color %>-muted)" d="<%= self.command(100) %>" />
<% if value_percent > 0 %>
<path transform="scale(-1,1) rotate(-90)" transform-origin="50% 50%" fill="var(--color-<%= donut_color %>-emphasis)" d="<%= self.command(self.value_percent) %>" />
<% end %>
</svg>
<%= render(Primer::Beta::Text.new(position: :absolute, color: color, tag: :p, mb: 0, font_size: font_size, font_weight: :bold, style: "top:50%;left:50%;transform:translate(-50%,-50%)")) do %>
<% if total.nonzero? %>
<%= value_percent %><%= render(Primer::Beta::Text.new(tag: :span, font_size: 4, font_weight: :bold)) { "%" } %>
<% else %>
<%= number_to_human(value, format: "%n%u", units: { thousand: "k", million: "M", billion: "G", trillion: "T" }) %>
<% end %>
<% end %>
</div>
class DonutGraphComponent < ApplicationComponent
attr_reader :value, :total, :value_percent, :size, :font_size
def initialize(value:, total:, size: 100, font_size: 3)
@value = value
@total = total
@size = size
@font_size = font_size
@value_percent = if @total > 0
((@value.to_f / @total.to_f) * 100).floor
else
0
end
end
def donut_color
case color
when :muted then "neutral"
else color
end
end
def color
case total.nonzero? && value_percent
when 100 then return :success
when 0 then return :danger
end
return :danger if value.zero?
:muted
end
private def coords(angle, radius)
x = Math.cos(angle * Math::PI / 180)
y = Math.sin(angle * Math::PI / 180)
"#{ x * radius + 100 / 2 } #{ y * -radius + 100 / 2}"
end
def command(percent)
degrees = (percent * 3.6) - 3
offset = degrees > 180 ? 1 : 0
radius = 50
inner_radius = 42
out = []
out << "M 100 50"
out << "A 50 50 0 #{ offset } 0 #{ self.coords(degrees, radius) }"
out << "A 1 1 0 1 0 #{ self.coords(degrees, inner_radius) }"
out << "L #{ self.coords(degrees, inner_radius) }"
out << "A #{ inner_radius } #{ inner_radius } 0 #{ offset } 1 #{ 50 + inner_radius } 50"
out << "A 1 1 0 0 0 100 50"
out.join(" ")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment