diff --git a/v3/utils.go b/v3/utils.go index e53d5316..ad16e41e 100644 --- a/v3/utils.go +++ b/v3/utils.go @@ -108,6 +108,21 @@ func GetMaxFloat64From2dSlice(slices [][]float64) (float64, error) { return max, nil } +func GetMinFloat64From2dSlice(slices [][]float64) (float64, error) { + if len(slices) == 0 || len(slices[0]) == 0 { + return 0, fmt.Errorf("cannot get min value from empty slice") + } + min := slices[0][0] + for _, slice := range slices { + for _, val := range slice { + if val < min { + min = val + } + } + } + return min, nil +} + func RoundFloat64(x float64) float64 { return math.Floor(x + 0.5) } diff --git a/v3/widgets/plot.go b/v3/widgets/plot.go index f4870de8..274af27f 100644 --- a/v3/widgets/plot.go +++ b/v3/widgets/plot.go @@ -20,6 +20,7 @@ type Plot struct { Data [][]float64 DataLabels []string + MinVal float64 MaxVal float64 LineColors []Color @@ -76,15 +77,16 @@ func NewPlot() *Plot { } } -func (self *Plot) renderBraille(buf *Buffer, drawArea image.Rectangle, maxVal float64) { +func (self *Plot) renderBraille(buf *Buffer, drawArea image.Rectangle, minVal float64, maxVal float64) { canvas := NewCanvas() canvas.Rectangle = drawArea + h := maxVal - minVal switch self.PlotType { case ScatterPlot: for i, line := range self.Data { for j, val := range line { - height := int((val / maxVal) * float64(drawArea.Dy()-1)) + height := int(((val - minVal) / h) * float64(drawArea.Dy()-1)) canvas.SetPoint( image.Pt( (drawArea.Min.X+(j*self.HorizontalScale))*2, @@ -98,7 +100,7 @@ func (self *Plot) renderBraille(buf *Buffer, drawArea image.Rectangle, maxVal fl for i, line := range self.Data { previousHeight := int((line[1] / maxVal) * float64(drawArea.Dy()-1)) for j, val := range line[1:] { - height := int((val / maxVal) * float64(drawArea.Dy()-1)) + height := int(((val - minVal) / h) * float64(drawArea.Dy()-1)) canvas.SetLine( image.Pt( (drawArea.Min.X+(j*self.HorizontalScale))*2, @@ -118,12 +120,13 @@ func (self *Plot) renderBraille(buf *Buffer, drawArea image.Rectangle, maxVal fl canvas.Draw(buf) } -func (self *Plot) renderDot(buf *Buffer, drawArea image.Rectangle, maxVal float64) { +func (self *Plot) renderDot(buf *Buffer, drawArea image.Rectangle, minVal float64, maxVal float64) { + h := maxVal - minVal switch self.PlotType { case ScatterPlot: for i, line := range self.Data { for j, val := range line { - height := int((val / maxVal) * float64(drawArea.Dy()-1)) + height := int(((val - minVal) / h) * float64(drawArea.Dy()-1)) point := image.Pt(drawArea.Min.X+(j*self.HorizontalScale), drawArea.Max.Y-1-height) if point.In(drawArea) { buf.SetCell( @@ -137,7 +140,7 @@ func (self *Plot) renderDot(buf *Buffer, drawArea image.Rectangle, maxVal float6 for i, line := range self.Data { for j := 0; j < len(line) && j*self.HorizontalScale < drawArea.Dx(); j++ { val := line[j] - height := int((val / maxVal) * float64(drawArea.Dy()-1)) + height := int(((val - maxVal) / h) * float64(drawArea.Dy()-1)) buf.SetCell( NewCell(self.DotMarkerRune, NewStyle(SelectColor(self.LineColors, i))), image.Pt(drawArea.Min.X+(j*self.HorizontalScale), drawArea.Max.Y-1-height), @@ -147,7 +150,8 @@ func (self *Plot) renderDot(buf *Buffer, drawArea image.Rectangle, maxVal float6 } } -func (self *Plot) plotAxes(buf *Buffer, maxVal float64) { +func (self *Plot) plotAxes(buf *Buffer, minVal float64, maxVal float64) { + h := maxVal - minVal // draw origin cell buf.SetCell( NewCell(BOTTOM_LEFT, NewStyle(ColorWhite)), @@ -188,10 +192,11 @@ func (self *Plot) plotAxes(buf *Buffer, maxVal float64) { x += (len(label) + xAxisLabelsGap) * self.HorizontalScale } // draw y axis labels - verticalScale := maxVal / float64(self.Inner.Dy()-xAxisLabelsHeight-1) + verticalScale := h / float64(self.Inner.Dy()-xAxisLabelsHeight-1) for i := 0; i*(yAxisLabelsGap+1) < self.Inner.Dy()-1; i++ { + yVal := minVal + float64(i)*verticalScale*(yAxisLabelsGap+1) buf.SetString( - fmt.Sprintf("%.2f", float64(i)*verticalScale*(yAxisLabelsGap+1)), + fmt.Sprintf("%.2f", yVal), NewStyle(ColorWhite), image.Pt(self.Inner.Min.X, self.Inner.Max.Y-(i*(yAxisLabelsGap+1))-2), ) @@ -202,12 +207,14 @@ func (self *Plot) Draw(buf *Buffer) { self.Block.Draw(buf) maxVal := self.MaxVal + minVal := self.MinVal if maxVal == 0 { maxVal, _ = GetMaxFloat64From2dSlice(self.Data) + minVal, _ = GetMinFloat64From2dSlice(self.Data) } if self.ShowAxes { - self.plotAxes(buf, maxVal) + self.plotAxes(buf, minVal, maxVal) } drawArea := self.Inner @@ -220,8 +227,8 @@ func (self *Plot) Draw(buf *Buffer) { switch self.Marker { case MarkerBraille: - self.renderBraille(buf, drawArea, maxVal) + self.renderBraille(buf, drawArea, minVal, maxVal) case MarkerDot: - self.renderDot(buf, drawArea, maxVal) + self.renderDot(buf, drawArea, minVal, maxVal) } }