Skip to content

Commit

Permalink
feat(stats): two-column layout, add min height for bars graphs
Browse files Browse the repository at this point in the history
  • Loading branch information
believer committed Apr 13, 2024
1 parent 957071e commit 0577356
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 83 deletions.
34 changes: 17 additions & 17 deletions components/graphs.templ
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ templ Graph(data []types.Bar, title string) {
<svg role="img" viewBox="0 0 536 200">
for _, bar := range data {
<g
class="fill-neutral-100 [@media(any-hover:hover){&:hover}]:fill-neutral-200 dark:fill-neutral-800 dark:[@media(any-hover:hover){&:hover}]:fill-neutral-700"
_="on mouseenter add .opacity-100 to <text /> in me
class="fill-neutral-100 [@media(any-hover:hover){&:hover}]:fill-neutral-200 dark:fill-neutral-800 dark:[@media(any-hover:hover){&:hover}]:fill-neutral-700"
_="on mouseenter add .opacity-100 to <text /> in me
on mouseleave remove .opacity-100 from <text /> in me
on touchstart
add .opacity-100 to <text /> in me
Expand All @@ -23,27 +23,27 @@ templ Graph(data []types.Bar, title string) {
end"
>
<rect
class="stroke-neutral-400 dark:stroke-neutral-600 transition-transform delay-200 duration-1000 scale-y-0 origin-[center_180px]"
_="init add .scale-y-100 to me"
width={ strconv.Itoa(bar.BarWidth) }
height={ strconv.Itoa(bar.BarHeight) }
stroke-dasharray="4 2"
rx="2"
ry="2"
x={ strconv.Itoa(bar.BarX) }
y={ strconv.Itoa(bar.BarY) }
class="stroke-neutral-400 dark:stroke-neutral-600 transition-transform delay-200 duration-1000 scale-y-0 origin-[center_180px]"
_="init add .scale-y-100 to me"
width={ strconv.Itoa(bar.BarWidth) }
height={ strconv.Itoa(bar.BarHeight) }
stroke-dasharray="4 2"
rx="2"
ry="2"
x={ strconv.Itoa(bar.BarX) }
y={ strconv.Itoa(bar.BarY) }
></rect>
<text
class="opacity-0 transition-opacity fill-neutral-400 dark:fill-neutral-400 text-sm tabular-nums"
x={ strconv.FormatFloat(bar.LabelX, 'f', 2, 64) }
y={ strconv.FormatFloat(bar.LabelY, 'f', 2, 64) }
class="opacity-0 transition-opacity fill-neutral-400 dark:fill-neutral-400 text-sm tabular-nums"
x={ strconv.FormatFloat(bar.LabelX, 'f', 2, 64) }
y={ strconv.FormatFloat(bar.LabelY, 'f', 2, 64) }
>
{ bar.Label }
</text>
<text
class="fill-neutral-400 dark:fill-neutral-500 opacity-0 transition-opacity text-sm tabular-nums"
x={ strconv.FormatFloat(bar.ValueX, 'f', 2, 64) }
y={ strconv.Itoa(bar.ValueY) }
class="fill-neutral-400 dark:fill-neutral-500 opacity-0 transition-opacity text-sm tabular-nums"
x={ strconv.FormatFloat(bar.ValueX, 'f', 2, 64) }
y={ strconv.Itoa(bar.ValueY) }
>
{ strconv.Itoa(bar.Value) }
</text>
Expand Down
12 changes: 11 additions & 1 deletion handlers/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ func getGraphWithQuery(query string) ([]types.Bar, error) {
return constructGraphFromData(data)
}

func clamp(val, min, max int) int {
if val < min {
return min
}
if val > max {
return max
}
return val
}

func constructGraphFromData(data []types.GraphData) ([]types.Bar, error) {
var graphData []types.Bar

Expand All @@ -142,7 +152,7 @@ func constructGraphFromData(data []types.GraphData) ([]types.Bar, error) {
elementsInGraph = graphWidth / len(data)
// Calcualte the bar Height
// Subtract 40 from the graph height to make room for the labels
barHeight = int(float64(row.Value)/float64(maxCount.Value)*float64(graphHeight-40)) - 6
barHeight = clamp(int(float64(row.Value)/float64(maxCount.Value)*float64(graphHeight-46)), 2, graphHeight-40)
barWidth = int(float64(graphWidth)/float64(len(data))) - 5

// Space the bars evenly across the graph
Expand Down
2 changes: 1 addition & 1 deletion public/styles.css

Large diffs are not rendered by default.

128 changes: 67 additions & 61 deletions views/stats.templ
Original file line number Diff line number Diff line change
Expand Up @@ -9,80 +9,86 @@ import (

templ Stats(stats types.Stats, formattedTotalRuntime string, mostWatchedCast []components.ListItem, watchedByYear []types.Bar, ratings []types.Bar, yearRatings []types.Bar, mostWatchedMovies []components.ListItem, seenThisYear []types.Bar, bestOfTheYear types.Movie) {
@Layout("Stats", "") {
<div class="mx-auto flex max-w-xl flex-col gap-8 px-5 py-8">
<div class="mx-auto flex max-w-xl lg:max-w-5xl flex-col gap-y-8 px-5 pb-8 pt-8 lg:pt-24">
<nav class="flex items-center gap-5">
<div class="left-8 top-10 md:absolute">
@components.Link("/", "") {
Home
}
</div>
</nav>
@components.Section("Stats", 0) {
@components.DescriptionList() {
@components.DescriptionListItem("Unique movies seen", true) {
{ strconv.Itoa(stats.UniqueMovies) }
}
@components.DescriptionListItem("Movies seen with rewatches", true) {
{ strconv.Itoa(stats.SeenWithRewatches) }
}
@components.DescriptionListItem("Time watched", true) {
{ formattedTotalRuntime }
}
@components.DescriptionListItem("Top IMDb rating", true) {
<a
class="border-b border-dashed border-neutral-500 focus:outline-none focus-visible:rounded-sm focus-visible:outline-dashed focus-visible:outline-offset-2 focus-visible:outline-neutral-400 dark:border-neutral-400 dark:focus-visible:outline-neutral-600"
href={ templ.URL(fmt.Sprintf("/movies/%s", stats.TopImdbID)) }
>
{ stats.TopImdbTitle }
</a>
<span class="text-xs">
({ strconv.FormatFloat(stats.TopImdbRating, 'f', 1, 64) })
</span>
}
if bestOfTheYear.ID != 0 {
@components.DescriptionListItem("Best of the Year", true) {
@components.Link(fmt.Sprintf("/movies/%d", bestOfTheYear.ID), "") {
{ bestOfTheYear.Title }
<div class="grid grid-cols-1 lg:grid-cols-2 gap-10">
<div class="flex flex-col gap-y-8">
@components.Section("Stats", 0) {
@components.DescriptionList() {
@components.DescriptionListItem("Unique movies seen", true) {
{ strconv.Itoa(stats.UniqueMovies) }
}
@components.DescriptionListItem("Movies seen with rewatches", true) {
{ strconv.Itoa(stats.SeenWithRewatches) }
}
@components.DescriptionListItem("Time watched", true) {
{ formattedTotalRuntime }
}
if bestOfTheYear.Rating.Valid {
@components.DescriptionListItem("Top IMDb rating", true) {
<a
class="border-b border-dashed border-neutral-500 focus:outline-none focus-visible:rounded-sm focus-visible:outline-dashed focus-visible:outline-offset-2 focus-visible:outline-neutral-400 dark:border-neutral-400 dark:focus-visible:outline-neutral-600"
href={ templ.URL(fmt.Sprintf("/movies/%s", stats.TopImdbID)) }
>
{ stats.TopImdbTitle }
</a>
<span class="text-xs">
({ strconv.FormatInt(bestOfTheYear.Rating.Int64, 16) })
({ strconv.FormatFloat(stats.TopImdbRating, 'f', 1, 64) })
</span>
}
if bestOfTheYear.ID != 0 {
@components.DescriptionListItem("Best of the Year", true) {
@components.Link(fmt.Sprintf("/movies/%d", bestOfTheYear.ID), "") {
{ bestOfTheYear.Title }
}
if bestOfTheYear.Rating.Valid {
<span class="text-xs">
({ strconv.FormatInt(bestOfTheYear.Rating.Int64, 16) })
</span>
}
}
}
}
}
}
}
@components.Graph(watchedByYear, "Watched by year")
@components.Graph(ratings, "Ratings")
@components.Graph(yearRatings, "Ratings this year")
@components.Graph(seenThisYear, "Seen this year by month")
@components.Section("Most watched movies", 0) {
@components.OrderedList(mostWatchedMovies, "movie")
}
@components.Section("Cast", 0) {
@components.OrderedList(mostWatchedCast, "person")
}
<section
hx-get="/stats/most-watched-person/director"
hx-trigger="load"
hx-swap="outerHTML"
></section>
<section
hx-get="/stats/most-watched-person/writer"
hx-trigger="load"
hx-swap="outerHTML"
></section>
<section
hx-get="/stats/most-watched-person/composer"
hx-trigger="load"
hx-swap="outerHTML"
></section>
<section
hx-get="/stats/most-watched-person/producer"
hx-trigger="load"
hx-swap="outerHTML"
></section>
@components.Graph(watchedByYear, "Watched by year")
@components.Graph(ratings, "Ratings")
@components.Graph(yearRatings, "Ratings this year")
@components.Graph(seenThisYear, "Seen this year by month")
</div>
<div class="flex flex-col gap-y-8">
@components.Section("Most watched movies", 0) {
@components.OrderedList(mostWatchedMovies, "movie")
}
@components.Section("Cast", 0) {
@components.OrderedList(mostWatchedCast, "person")
}
<section
hx-get="/stats/most-watched-person/director"
hx-trigger="load"
hx-swap="outerHTML"
></section>
<section
hx-get="/stats/most-watched-person/writer"
hx-trigger="load"
hx-swap="outerHTML"
></section>
<section
hx-get="/stats/most-watched-person/composer"
hx-trigger="load"
hx-swap="outerHTML"
></section>
<section
hx-get="/stats/most-watched-person/producer"
hx-trigger="load"
hx-swap="outerHTML"
></section>
</div>
</div>
</div>
}
}
10 changes: 7 additions & 3 deletions views/stats_templ.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0577356

Please sign in to comment.