From e7a49c2c212d022df6cda4739b8c3e391fd3594a Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Thu, 4 May 2023 12:52:28 -0600 Subject: [PATCH 1/5] Improvements to how the X Axis is rendered This provides two improvements to how the X Axis is rendered: * The calculation for where a tick should exist has been improved. It now will ensure a tick is always at both the start of the axis and the end of the axis. This makes it clear exactly what data span is captured in the graph. * The second improvement is how the label on the last tick is written. It used to often get partially cut off, and with the change to ensure a tick is always at the end this could be seen more easily. Now the last tick has it's label written to the left so that it can be fully displayed. --- painter.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/painter.go b/painter.go index 18496fd..450afdf 100644 --- a/painter.go +++ b/painter.go @@ -615,6 +615,17 @@ func (p *Painter) TextFit(body string, x, y, width int, textAligns ...string) ch return output } +func isTick(totalRange int, numTicks int, index int) bool { + step := float64(totalRange-1) / float64(numTicks-1) + for i := 0; i < numTicks; i++ { + value := float64(i) * step + if int(value + 0.5) == index { + return true + } + } + return false +} + func (p *Painter) Ticks(opt TicksOption) *Painter { if opt.Count <= 0 || opt.Length <= 0 { return p @@ -638,7 +649,7 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { if index < first { continue } - if (index-first)%unit != 0 { + if ! isTick(len(values), unit, index) { continue } if isVertical { @@ -674,15 +685,13 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } count := len(opt.TextList) positionCenter := true - showIndex := opt.Unit / 2 + tickLimit := true if containsString([]string{ PositionLeft, PositionTop, }, opt.Position) { positionCenter = false count-- - // 非居中 - showIndex = 0 } width := p.Width() height := p.Height() @@ -690,6 +699,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { isVertical := opt.Orient == OrientVertical if isVertical { values = autoDivide(height, count) + tickLimit = false } else { values = autoDivide(width, count) } @@ -699,7 +709,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { if index < opt.First { continue } - if opt.Unit != 0 && (index-opt.First)%opt.Unit != showIndex { + if opt.Unit != 0 && tickLimit && ! isTick(len(opt.TextList)-opt.First, opt.Unit, index-opt.First) { continue } if isTextRotation { @@ -724,7 +734,11 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { x = 0 } } else { - x = start - box.Width()>>1 + if index == len(opt.TextList) - 1 { + x = start - box.Width() + } else { + x = start - box.Width()>>1 + } } x += offset.Left y += offset.Top @@ -749,7 +763,6 @@ func (p *Painter) Grid(opt GridOption) *Painter { x1 := 0 y1 := 0 if isVertical { - x0 = v x1 = v y1 = height From 19173dfd37f737b4a5a681556b0a7fe661d749db Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Thu, 4 May 2023 17:59:11 -0600 Subject: [PATCH 2/5] painter.go: Optimize isTick function This reduces the loop frequency to one or two iterations in all cases. I have been unable to find any single line equation that can produce this same behavior, but one likely exists. --- painter.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/painter.go b/painter.go index 450afdf..175b2f2 100644 --- a/painter.go +++ b/painter.go @@ -617,10 +617,12 @@ func (p *Painter) TextFit(body string, x, y, width int, textAligns ...string) ch func isTick(totalRange int, numTicks int, index int) bool { step := float64(totalRange-1) / float64(numTicks-1) - for i := 0; i < numTicks; i++ { - value := float64(i) * step - if int(value + 0.5) == index { + for i := int(float64(index) / step); i < numTicks; i++ { + value := int((float64(i) * step) + 0.5) + if value == index { return true + } else if value > index { + break } } return false From c810369730585ed5692c3e5e13c36a4c785568c1 Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Fri, 5 May 2023 09:44:09 -0600 Subject: [PATCH 3/5] Change ticks to avoid values impacting each other The recently introduced logic has an incorrect understanding of the `unit` parameter. This would result in too many ticks being outputted, particularly as datasets got larger. This fixes it by re-calculating the tick count using the `unit` param as originally intended. --- painter.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/painter.go b/painter.go index 175b2f2..d74b80d 100644 --- a/painter.go +++ b/painter.go @@ -615,7 +615,8 @@ func (p *Painter) TextFit(body string, x, y, width int, textAligns ...string) ch return output } -func isTick(totalRange int, numTicks int, index int) bool { +func isTick(totalRange int, unit int, index int) bool { + numTicks := (totalRange / unit) + 1 step := float64(totalRange-1) / float64(numTicks-1) for i := int(float64(index) / step); i < numTicks; i++ { value := int((float64(i) * step) + 0.5) @@ -737,7 +738,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } } else { if index == len(opt.TextList) - 1 { - x = start - box.Width() + x = start - box.Width() + 10 } else { x = start - box.Width()>>1 } From a158191faf90eb0baa08d2cbe8692afd5b09b58c Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Fri, 5 May 2023 09:55:55 -0600 Subject: [PATCH 4/5] Add `Unit` to XAxis as a publicly visible parameter In some cases the XAxis may have a single long title. This can result in very few increments being shown. In order to be more flexible for those cases this allows the XAxis Tick frequency to be able to be directly controlled. --- axis.go | 1 - xaxis.go | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/axis.go b/axis.go index 762a6a2..af104a8 100644 --- a/axis.go +++ b/axis.go @@ -176,7 +176,6 @@ func (a *axisPainter) Render() (Box, error) { unit := opt.Unit if unit <= 0 { - unit = ceilFloatToInt(float64(dataCount) / float64(fitTextCount)) unit = chart.MaxInt(unit, opt.SplitNumber) // 偶数 diff --git a/xaxis.go b/xaxis.go index 61698d7..5557015 100644 --- a/xaxis.go +++ b/xaxis.go @@ -40,7 +40,7 @@ type XAxisOption struct { FontSize float64 // The flag for show axis, set this to *false will hide axis Show *bool - // Number of segments that the axis is split into. Note that this number serves only as a recommendation. + // Number of segments that the axis is split into. Note that this number serves only as a recommendation to avoid writing overlap. SplitNumber int // The position of axis, it can be 'top' or 'bottom' Position string @@ -55,6 +55,8 @@ type XAxisOption struct { // The offset of label LabelOffset Box isValueAxis bool + // This value overrides SplitNumber, specifying directly the frequency at which the axis is split into, higher numbers result in less ticks + Unit int } const defaultXAxisHeight = 30 @@ -90,6 +92,7 @@ func (opt *XAxisOption) ToAxisOption() AxisOption { TextRotation: opt.TextRotation, LabelOffset: opt.LabelOffset, FirstAxis: opt.FirstAxis, + Unit: opt.Unit, } if opt.isValueAxis { axisOpt.SplitLineShow = true From 687baad0af8ff907e1e317f2671e3d76b91746c1 Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Fri, 5 May 2023 10:19:01 -0600 Subject: [PATCH 5/5] Unit test fixes Unit tests updated for new tick positions and in a couple cases additional one X axis sample. --- axis_test.go | 6 +++--- bar_chart_test.go | 2 +- chart_option_test.go | 6 +++--- echarts_test.go | 2 +- horizontal_bar_chart_test.go | 2 +- line_chart_test.go | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/axis_test.go b/axis_test.go index d0cff41..a04024d 100644 --- a/axis_test.go +++ b/axis_test.go @@ -53,7 +53,7 @@ func TestAxis(t *testing.T) { }).Render() return p.Bytes() }, - result: "\\nMonTueWedThuFriSatSun", + result: "\\nMonTueWedThuFriSatSun", }, // 底部x轴文本居左 { @@ -72,7 +72,7 @@ func TestAxis(t *testing.T) { }).Render() return p.Bytes() }, - result: "\\nMonTueWedThuFriSatSun", + result: "\\nMonTueWedThuFriSatSun", }, // 左侧y轴 { @@ -155,7 +155,7 @@ func TestAxis(t *testing.T) { }).Render() return p.Bytes() }, - result: "\\nMon --Tue --Wed --Thu --Fri --Sat --Sun --", + result: "\\nMon --Tue --Wed --Thu --Fri --Sat --Sun --", }, } diff --git a/bar_chart_test.go b/bar_chart_test.go index e1522d6..aec6428 100644 --- a/bar_chart_test.go +++ b/bar_chart_test.go @@ -102,7 +102,7 @@ func TestBarChart(t *testing.T) { } return p.Bytes() }, - result: "\\n24020016012080400FebMayAugNov24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", + result: "\\n24020016012080400JanAprJulSepDec24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", }, } diff --git a/chart_option_test.go b/chart_option_test.go index ff17750..b7f4e93 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -204,7 +204,7 @@ func TestLineRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) + assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) } func TestBarRender(t *testing.T) { @@ -277,7 +277,7 @@ func TestBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporation24020016012080400JanAprJulSepDec162.22182.22.341.6248.07", string(data)) } func TestHorizontalBarRender(t *testing.T) { @@ -326,7 +326,7 @@ func TestHorizontalBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", string(data)) + assert.Equal("\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", string(data)) } func TestPieRender(t *testing.T) { diff --git a/echarts_test.go b/echarts_test.go index 2ce1715..dd7562f 100644 --- a/echarts_test.go +++ b/echarts_test.go @@ -578,5 +578,5 @@ func TestRenderEChartsToSVG(t *testing.T) { ] }`) assert.Nil(err) - assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400JanAprJulSepDec162.22182.22.341.6248.07", string(data)) } diff --git a/horizontal_bar_chart_test.go b/horizontal_bar_chart_test.go index e078c4a..78f3e69 100644 --- a/horizontal_bar_chart_test.go +++ b/horizontal_bar_chart_test.go @@ -83,7 +83,7 @@ func TestHorizontalBarChart(t *testing.T) { } return p.Bytes() }, - result: "\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", + result: "\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", }, } for _, tt := range tests { diff --git a/line_chart_test.go b/line_chart_test.go index e169f90..e8bc1d7 100644 --- a/line_chart_test.go +++ b/line_chart_test.go @@ -117,7 +117,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, { render: func(p *Painter) ([]byte, error) { @@ -201,7 +201,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, }