Created
January 27, 2023 09:21
-
-
Save simon-engledew/6dfab9a14c98d5e34caf7d914d7cde2a to your computer and use it in GitHub Desktop.
Donut Graph
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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