Skip to content

Commit 0816b23

Browse files
committed
show time division and a time bar graph indicator
1 parent 9c6b16c commit 0816b23

File tree

2 files changed

+152
-44
lines changed

2 files changed

+152
-44
lines changed

core/core.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ func (s Score) StackedGraphPerBand() []BandGraph {
549549
DataPoints: make([]BandScore, len(graph.DataPoints)),
550550
Max: graph.Max,
551551
startTime: graph.startTime,
552+
duration: graph.duration,
552553
binSeconds: graph.binSeconds,
553554
}
554555

@@ -575,6 +576,7 @@ type BandGraph struct {
575576
Max BandScore
576577

577578
startTime time.Time
579+
duration time.Duration
578580
binSeconds float64
579581
}
580582

@@ -592,6 +594,7 @@ func NewBandGraph(band Band, startTime time.Time, duration time.Duration) BandGr
592594

593595
binSeconds: duration.Seconds() / float64(binCount),
594596
startTime: startTime,
597+
duration: duration,
595598
}
596599
}
597600

@@ -622,6 +625,7 @@ func (g BandGraph) Copy() BandGraph {
622625
DataPoints: make([]BandScore, len(g.DataPoints)),
623626
Max: g.Max,
624627
startTime: g.startTime,
628+
duration: g.duration,
625629
binSeconds: g.binSeconds,
626630
}
627631

@@ -690,6 +694,37 @@ func (g BandGraph) ScaleHourlyGoalToBin(goal int) float64 {
690694
return (g.binSeconds / 3600.0) * float64(goal)
691695
}
692696

697+
func (g BandGraph) ElapsedTime(timestamp time.Time) time.Duration {
698+
if g.startTime.IsZero() {
699+
return 0
700+
}
701+
if timestamp.IsZero() {
702+
return 0
703+
}
704+
if timestamp.Before(g.startTime) {
705+
return 0
706+
}
707+
708+
return timestamp.Sub(g.startTime)
709+
}
710+
711+
func (g BandGraph) ElapsedTimePercent(timestamp time.Time) float64 {
712+
if g.startTime.IsZero() {
713+
return 0
714+
}
715+
if g.duration == 0 {
716+
return 0
717+
}
718+
if timestamp.IsZero() {
719+
return 0
720+
}
721+
if timestamp.Before(g.startTime) {
722+
return 0
723+
}
724+
725+
return float64(g.ElapsedTime(timestamp)) / float64(g.duration)
726+
}
727+
693728
type BandScore struct {
694729
QSOs int
695730
Duplicates int

ui/scoreGraph.go

Lines changed: 117 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ui
22

33
import (
4+
"fmt"
45
"math"
56

67
"github.com/gotk3/gotk3/cairo"
@@ -12,6 +13,8 @@ import (
1213

1314
const useCurvedGraph = false
1415

16+
const timeIndicatorColorName = "hellocontest-timeindicator"
17+
1518
type scoreGraphStyle struct {
1619
colorProvider
1720

@@ -21,6 +24,8 @@ type scoreGraphStyle struct {
2124
timeFrameColor style.Color
2225
areaAlpha float64
2326
borderAlpha float64
27+
28+
fontSize float64
2429
}
2530

2631
func (s *scoreGraphStyle) Refresh() {
@@ -46,13 +51,12 @@ type scoreGraph struct {
4651
useCurvedGraph bool
4752
}
4853

49-
const timeIndicatorColorName = "hellocontest-timeindicator"
50-
5154
func newScoreGraph(colors colorProvider, clock core.Clock) *scoreGraph {
5255
style := &scoreGraphStyle{
5356
colorProvider: colors,
5457
areaAlpha: 0.4,
5558
borderAlpha: 0.8,
59+
fontSize: 15,
5660
}
5761
style.Refresh()
5862

@@ -123,6 +127,11 @@ type graphLayout struct {
123127
pointsLowZoneHeight float64
124128
multisLowZoneHeight float64
125129
binWidth float64
130+
divX []float64
131+
axisWidth float64
132+
divisionWidth float64
133+
leftLegendWidth float64
134+
timeIndicatorHeight float64
126135
}
127136

128137
func (g *scoreGraph) Draw(da *gtk.DrawingArea, cr *cairo.Context) {
@@ -135,27 +144,10 @@ func (g *scoreGraph) Draw(da *gtk.DrawingArea, cr *cairo.Context) {
135144
if len(g.graphs) > 0 {
136145
valueCount = len(g.graphs[0].DataPoints)
137146
}
138-
layout := g.calculateLayout(da, valueCount)
147+
layout := g.calculateLayout(da, cr, valueCount)
139148

140-
// the background
141149
g.fillBackground(cr)
142-
143-
// the zone
144-
cr.SetSourceRGBA(g.style.lowZoneColor.WithAlpha(g.style.areaAlpha))
145-
cr.MoveTo(0, layout.zeroY-layout.pointsLowZoneHeight)
146-
cr.LineTo(layout.width, layout.zeroY-layout.pointsLowZoneHeight)
147-
cr.LineTo(layout.width, layout.zeroY+layout.multisLowZoneHeight)
148-
cr.LineTo(0, layout.zeroY+layout.multisLowZoneHeight)
149-
cr.ClosePath()
150-
cr.Fill()
151-
152-
cr.SetSourceRGBA(g.style.lowZoneColor.WithAlpha(g.style.borderAlpha))
153-
cr.MoveTo(0, layout.zeroY-layout.pointsLowZoneHeight)
154-
cr.LineTo(layout.width, layout.zeroY-layout.pointsLowZoneHeight)
155-
cr.LineTo(layout.width, layout.zeroY+layout.multisLowZoneHeight)
156-
cr.LineTo(0, layout.zeroY+layout.multisLowZoneHeight)
157-
cr.ClosePath()
158-
cr.Stroke()
150+
g.drawLowZone(cr, layout)
159151

160152
// the graph
161153
for i := len(g.graphs) - 1; i >= 0; i-- {
@@ -170,34 +162,25 @@ func (g *scoreGraph) Draw(da *gtk.DrawingArea, cr *cairo.Context) {
170162
}
171163
}
172164

173-
// the time frame
174-
if g.timeFrameIndex >= 0 && valueCount > 1 {
175-
startX := float64(g.timeFrameIndex) * layout.binWidth
176-
endX := float64(g.timeFrameIndex+1) * layout.binWidth
177-
cr.SetSourceRGBA(g.style.timeFrameColor.ToRGBA())
178-
cr.MoveTo(startX, layout.zeroY-layout.maxHeight)
179-
cr.LineTo(endX, layout.zeroY-layout.maxHeight)
180-
cr.LineTo(endX, layout.zeroY+layout.maxHeight)
181-
cr.LineTo(startX, layout.zeroY+layout.maxHeight)
182-
cr.ClosePath()
183-
cr.Stroke()
184-
}
185-
186-
// the zero line
187-
cr.SetSourceRGB(g.style.axisColor.ToRGB())
188-
cr.MoveTo(0, layout.zeroY)
189-
cr.LineTo(layout.width, layout.zeroY)
190-
cr.Stroke()
165+
g.drawTimeDivisions(cr, layout)
166+
g.drawTimeIndicator(cr, layout)
167+
g.drawZeroLine(cr, layout)
191168
}
192169

193-
func (g *scoreGraph) calculateLayout(da *gtk.DrawingArea, valueCount int) graphLayout {
170+
func (g *scoreGraph) calculateLayout(da *gtk.DrawingArea, cr *cairo.Context, valueCount int) graphLayout {
194171
result := graphLayout{
195-
width: float64(da.GetAllocatedWidth()),
196-
height: float64(da.GetAllocatedHeight()),
197-
marginY: 5.0,
172+
width: float64(da.GetAllocatedWidth()),
173+
height: float64(da.GetAllocatedHeight()),
174+
marginY: 10.0,
175+
axisWidth: 1.0,
176+
divisionWidth: .5,
198177
}
199178

200-
result.zeroY = result.height / 2.0
179+
cr.SetFontSize(g.style.fontSize)
180+
result.leftLegendWidth = cr.TextExtents("00:00").Width + 2.0
181+
result.timeIndicatorHeight = cr.TextExtents("Hg").Height + 2.0
182+
183+
result.zeroY = (result.height - result.timeIndicatorHeight) / 2.0
201184
result.maxHeight = result.zeroY - result.marginY
202185
result.pointsLowZoneHeight = math.Min(result.maxHeight/2.0, (result.maxHeight/float64(g.maxPoints))*g.pointsBinGoal)
203186
result.multisLowZoneHeight = math.Min(result.maxHeight/2.0, (result.maxHeight/float64(g.maxMultis))*g.multisBinGoal)
@@ -207,6 +190,15 @@ func (g *scoreGraph) calculateLayout(da *gtk.DrawingArea, valueCount int) graphL
207190
result.binWidth = result.width
208191
}
209192

193+
const divCount = 8
194+
if len(result.divX) != divCount {
195+
result.divX = make([]float64, divCount-1)
196+
}
197+
divWidth := result.width / float64(divCount)
198+
for i := range result.divX {
199+
result.divX[i] = float64(i+1) * divWidth
200+
}
201+
210202
return result
211203
}
212204

@@ -218,6 +210,87 @@ func (g *scoreGraph) fillBackground(cr *cairo.Context) {
218210
cr.Paint()
219211
}
220212

213+
func (g *scoreGraph) drawZeroLine(cr *cairo.Context, layout graphLayout) {
214+
cr.SetSourceRGB(g.style.axisColor.ToRGB())
215+
cr.SetLineWidth(layout.axisWidth)
216+
cr.MoveTo(0, layout.zeroY)
217+
cr.LineTo(layout.width, layout.zeroY)
218+
cr.Stroke()
219+
}
220+
221+
func (g *scoreGraph) drawTimeDivisions(cr *cairo.Context, layout graphLayout) {
222+
cr.SetSourceRGB(g.style.axisColor.ToRGB())
223+
cr.SetLineWidth(layout.divisionWidth)
224+
for _, x := range layout.divX {
225+
cr.MoveTo(x, layout.zeroY-layout.maxHeight)
226+
cr.LineTo(x, layout.zeroY+layout.maxHeight)
227+
cr.Stroke()
228+
}
229+
}
230+
231+
func (g *scoreGraph) drawTimeIndicator(cr *cairo.Context, layout graphLayout) {
232+
if g.timeFrameIndex <= 0 {
233+
return
234+
}
235+
now := g.clock.Now()
236+
237+
// the time bar
238+
elapsedTimePercent := g.graphs[0].ElapsedTimePercent(now)
239+
left := 0.0
240+
right := left + (layout.width-left)*elapsedTimePercent
241+
bottom := layout.height - layout.marginY
242+
top := bottom - layout.timeIndicatorHeight
243+
244+
cr.SetSourceRGBA(g.style.timeFrameColor.ToRGBA())
245+
cr.MoveTo(left, top)
246+
cr.LineTo(right, top)
247+
cr.LineTo(right, bottom)
248+
cr.LineTo(left, bottom)
249+
cr.ClosePath()
250+
cr.Fill()
251+
252+
// the elapsed time
253+
elapsedTime := g.graphs[0].ElapsedTime(now)
254+
elapsedTimeText := fmt.Sprintf("%02d:%02d", int(elapsedTime.Hours()), int(elapsedTime.Minutes())%60)
255+
256+
cr.SetSourceRGB(g.style.axisColor.ToRGB())
257+
cr.SetFontSize(g.style.fontSize)
258+
cr.MoveTo(1, layout.height-layout.marginY-1)
259+
cr.ShowText(elapsedTimeText)
260+
261+
// the old box
262+
startX := float64(g.timeFrameIndex) * layout.binWidth
263+
endX := float64(g.timeFrameIndex+1) * layout.binWidth
264+
265+
cr.SetSourceRGBA(g.style.timeFrameColor.ToRGBA())
266+
cr.SetLineWidth(layout.divisionWidth)
267+
cr.MoveTo(startX, layout.zeroY-layout.maxHeight)
268+
cr.LineTo(endX, layout.zeroY-layout.maxHeight)
269+
cr.LineTo(endX, layout.zeroY+layout.maxHeight)
270+
cr.LineTo(startX, layout.zeroY+layout.maxHeight)
271+
cr.ClosePath()
272+
cr.Stroke()
273+
}
274+
275+
func (g *scoreGraph) drawLowZone(cr *cairo.Context, layout graphLayout) {
276+
cr.SetSourceRGBA(g.style.lowZoneColor.WithAlpha(g.style.areaAlpha))
277+
cr.MoveTo(0, layout.zeroY-layout.pointsLowZoneHeight)
278+
cr.LineTo(layout.width, layout.zeroY-layout.pointsLowZoneHeight)
279+
cr.LineTo(layout.width, layout.zeroY+layout.multisLowZoneHeight)
280+
cr.LineTo(0, layout.zeroY+layout.multisLowZoneHeight)
281+
cr.ClosePath()
282+
cr.Fill()
283+
284+
cr.SetSourceRGBA(g.style.lowZoneColor.WithAlpha(g.style.borderAlpha))
285+
cr.SetLineWidth(layout.divisionWidth)
286+
cr.MoveTo(0, layout.zeroY-layout.pointsLowZoneHeight)
287+
cr.LineTo(layout.width, layout.zeroY-layout.pointsLowZoneHeight)
288+
cr.LineTo(layout.width, layout.zeroY+layout.multisLowZoneHeight)
289+
cr.LineTo(0, layout.zeroY+layout.multisLowZoneHeight)
290+
cr.ClosePath()
291+
cr.Stroke()
292+
}
293+
221294
func (g *scoreGraph) drawDataPointsRectangular(cr *cairo.Context, layout graphLayout, datapoints []core.BandScore) {
222295
valueCount := len(datapoints)
223296

0 commit comments

Comments
 (0)