1 package gofpdf
2
3 import (
4 "math"
5 "strconv"
6 )
7
8 func unused(args ...interface{}) {
9 }
10
11
12 type RGBType struct {
13 R, G, B int
14 }
15
16
17
18 type RGBAType struct {
19 R, G, B int
20 Alpha float64
21 }
22
23
24
25 type StateType struct {
26 clrDraw, clrText, clrFill RGBType
27 lineWd float64
28 fontSize float64
29 alpha float64
30 blendStr string
31 cellMargin float64
32 }
33
34
35 func StateGet(pdf *Fpdf) (st StateType) {
36 st.clrDraw.R, st.clrDraw.G, st.clrDraw.B = pdf.GetDrawColor()
37 st.clrFill.R, st.clrFill.G, st.clrFill.B = pdf.GetFillColor()
38 st.clrText.R, st.clrText.G, st.clrText.B = pdf.GetTextColor()
39 st.lineWd = pdf.GetLineWidth()
40 _, st.fontSize = pdf.GetFontSize()
41 st.alpha, st.blendStr = pdf.GetAlpha()
42 st.cellMargin = pdf.GetCellMargin()
43 return
44 }
45
46
47
48 func (st StateType) Put(pdf *Fpdf) {
49 pdf.SetDrawColor(st.clrDraw.R, st.clrDraw.G, st.clrDraw.B)
50 pdf.SetFillColor(st.clrFill.R, st.clrFill.G, st.clrFill.B)
51 pdf.SetTextColor(st.clrText.R, st.clrText.G, st.clrText.B)
52 pdf.SetLineWidth(st.lineWd)
53 pdf.SetFontUnitSize(st.fontSize)
54 pdf.SetAlpha(st.alpha, st.blendStr)
55 pdf.SetCellMargin(st.cellMargin)
56 }
57
58
59 type TickFormatFncType func(val float64, precision int) string
60
61
62 func defaultFormatter(val float64, precision int) string {
63 return strconv.FormatFloat(val, 'f', precision, 64)
64 }
65
66
67
68
69 type GridType struct {
70
71 x, y, w, h float64
72
73
74 xm, xb, ym, yb float64
75
76 xTicks, yTicks []float64
77
78 XLabelIn, YLabelIn bool
79
80 XLabelRotate bool
81
82 XTickStr, YTickStr TickFormatFncType
83
84 XDiv, YDiv int
85
86 xPrecision, yPrecision int
87
88 ClrText, ClrMain, ClrSub RGBAType
89
90 WdMain, WdSub float64
91
92 TextSize float64
93 }
94
95
96
97
98
99 func linear(x1, y1, x2, y2 float64) (slope, intercept float64) {
100 if x2 != x1 {
101 slope = (y2 - y1) / (x2 - x1)
102 intercept = y2 - x2*slope
103 }
104 return
105 }
106
107
108
109
110 func linearTickmark(tm []float64, lo, hi float64) (slope, intercept float64) {
111 ln := len(tm)
112 if ln > 0 {
113 slope, intercept = linear(tm[0], lo, tm[ln-1], hi)
114 }
115 return
116 }
117
118
119
120
121
122
123
124
125
126
127
128
129
130 func NewGrid(x, y, w, h float64) (grid GridType) {
131 grid.x = x
132 grid.y = y
133 grid.w = w
134 grid.h = h
135 grid.TextSize = 7
136 grid.TickmarksExtentX(0, 1, 1)
137 grid.TickmarksExtentY(0, 1, 1)
138 grid.XLabelIn = false
139 grid.YLabelIn = false
140 grid.XLabelRotate = false
141 grid.XDiv = 10
142 grid.YDiv = 10
143 grid.ClrText = RGBAType{R: 0, G: 0, B: 0, Alpha: 1}
144 grid.ClrMain = RGBAType{R: 128, G: 160, B: 128, Alpha: 1}
145 grid.ClrSub = RGBAType{R: 192, G: 224, B: 192, Alpha: 1}
146 grid.WdMain = 0.1
147 grid.WdSub = 0.1
148 grid.YTickStr = defaultFormatter
149 grid.XTickStr = defaultFormatter
150 return
151 }
152
153
154
155 func (g GridType) WdAbs(dataWd float64) float64 {
156 return math.Abs(g.xm * dataWd)
157 }
158
159
160
161 func (g GridType) Wd(dataWd float64) float64 {
162 return g.xm * dataWd
163 }
164
165
166
167 func (g GridType) XY(dataX, dataY float64) (x, y float64) {
168 return g.xm*dataX + g.xb, g.ym*dataY + g.yb
169 }
170
171
172
173
174
175 func (g GridType) Pos(xRel, yRel float64) (x, y float64) {
176 x = g.w*xRel + g.x
177 y = g.h*(1-yRel) + g.y
178 return
179 }
180
181
182
183 func (g GridType) X(dataX float64) float64 {
184 return g.xm*dataX + g.xb
185 }
186
187
188
189 func (g GridType) HtAbs(dataHt float64) float64 {
190 return math.Abs(g.ym * dataHt)
191 }
192
193
194
195 func (g GridType) Ht(dataHt float64) float64 {
196 return g.ym * dataHt
197 }
198
199
200
201 func (g GridType) Y(dataY float64) float64 {
202 return g.ym*dataY + g.yb
203 }
204
205
206
207
208 func (g GridType) XRange() (min, max float64) {
209 min = g.xTicks[0]
210 max = g.xTicks[len(g.xTicks)-1]
211 return
212 }
213
214
215
216
217 func (g GridType) YRange() (min, max float64) {
218 min = g.yTicks[0]
219 max = g.yTicks[len(g.yTicks)-1]
220 return
221 }
222
223
224
225
226
227
228
229
230 func (g *GridType) TickmarksContainX(min, max float64) {
231 g.xTicks, g.xPrecision = Tickmarks(min, max)
232 g.xm, g.xb = linearTickmark(g.xTicks, g.x, g.x+g.w)
233 }
234
235
236
237
238
239
240
241
242 func (g *GridType) TickmarksContainY(min, max float64) {
243 g.yTicks, g.yPrecision = Tickmarks(min, max)
244 g.ym, g.yb = linearTickmark(g.yTicks, g.y+g.h, g.y)
245 }
246
247 func extent(min, div float64, count int) (tm []float64, precision int) {
248 tm = make([]float64, count+1)
249 for j := 0; j <= count; j++ {
250 tm[j] = min
251 min += div
252 }
253 precision = TickmarkPrecision(div)
254 return
255 }
256
257
258
259
260
261
262
263
264 func (g *GridType) TickmarksExtentX(min, div float64, count int) {
265 g.xTicks, g.xPrecision = extent(min, div, count)
266 g.xm, g.xb = linearTickmark(g.xTicks, g.x, g.x+g.w)
267 }
268
269
270
271
272
273
274
275
276 func (g *GridType) TickmarksExtentY(min, div float64, count int) {
277 g.yTicks, g.yPrecision = extent(min, div, count)
278 g.ym, g.yb = linearTickmark(g.yTicks, g.y+g.h, g.y)
279 }
280
281
282
283
284
285
286
287
288
289 func lineAttr(pdf *Fpdf, clr RGBAType, lineWd float64) {
290 pdf.SetLineWidth(lineWd)
291 pdf.SetAlpha(clr.Alpha, "Normal")
292 pdf.SetDrawColor(clr.R, clr.G, clr.B)
293 }
294
295
296 func (g GridType) Grid(pdf *Fpdf) {
297 var st StateType
298 var yLen, xLen int
299 var textSz, halfTextSz, yMin, yMax, xMin, xMax, yDiv, xDiv float64
300 var str string
301 var strOfs, strWd, tp, bt, lf, rt, drawX, drawY float64
302
303 xLen = len(g.xTicks)
304 yLen = len(g.yTicks)
305 if xLen > 1 && yLen > 1 {
306
307 st = StateGet(pdf)
308
309 line := func(x1, y1, x2, y2 float64, heavy bool) {
310 if heavy {
311 lineAttr(pdf, g.ClrMain, g.WdMain)
312 } else {
313 lineAttr(pdf, g.ClrSub, g.WdSub)
314 }
315 pdf.Line(x1, y1, x2, y2)
316 }
317
318 textSz = pdf.PointToUnitConvert(g.TextSize)
319 halfTextSz = textSz / 2
320
321 pdf.SetAutoPageBreak(false, 0)
322 pdf.SetFontUnitSize(textSz)
323 strOfs = pdf.GetStringWidth("0")
324 pdf.SetFillColor(255, 255, 255)
325 pdf.SetCellMargin(0)
326
327 xMin = g.xTicks[0]
328 xMax = g.xTicks[xLen-1]
329
330 yMin = g.yTicks[0]
331 yMax = g.yTicks[yLen-1]
332
333 lf = g.X(xMin)
334 rt = g.X(xMax)
335 bt = g.Y(yMin)
336 tp = g.Y(yMax)
337
338
339 xDiv = g.xTicks[1] - g.xTicks[0]
340 if g.XDiv > 0 {
341 xDiv = xDiv / float64(g.XDiv)
342 }
343 xDiv = g.Wd(xDiv)
344 for j, x := range g.xTicks {
345 drawX = g.X(x)
346 line(drawX, tp, drawX, bt, true)
347 if j < xLen-1 {
348 for k := 1; k < g.XDiv; k++ {
349 drawX += xDiv
350 line(drawX, tp, drawX, bt, false)
351 }
352 }
353 }
354
355
356 yDiv = g.yTicks[1] - g.yTicks[0]
357 if g.YDiv > 0 {
358 yDiv = yDiv / float64(g.YDiv)
359 }
360 yDiv = g.Ht(yDiv)
361 for j, y := range g.yTicks {
362 drawY = g.Y(y)
363 line(lf, drawY, rt, drawY, true)
364 if j < yLen-1 {
365 for k := 1; k < g.YDiv; k++ {
366 drawY += yDiv
367 line(lf, drawY, rt, drawY, false)
368 }
369 }
370 }
371
372
373 if g.XTickStr != nil {
374 drawY = bt
375 for _, x := range g.xTicks {
376 str = g.XTickStr(x, g.xPrecision)
377 strWd = pdf.GetStringWidth(str)
378 drawX = g.X(x)
379 if g.XLabelRotate {
380 pdf.TransformBegin()
381 pdf.TransformRotate(90, drawX, drawY)
382 if g.XLabelIn {
383 pdf.SetXY(drawX+strOfs, drawY-halfTextSz)
384 } else {
385 pdf.SetXY(drawX-strOfs-strWd, drawY-halfTextSz)
386 }
387 pdf.CellFormat(strWd, textSz, str, "", 0, "L", true, 0, "")
388 pdf.TransformEnd()
389 } else {
390 drawX -= strWd / 2.0
391 if g.XLabelIn {
392 pdf.SetXY(drawX, drawY-textSz-strOfs)
393 } else {
394 pdf.SetXY(drawX, drawY+strOfs)
395 }
396 pdf.CellFormat(strWd, textSz, str, "", 0, "L", true, 0, "")
397 }
398 }
399 }
400
401
402 if g.YTickStr != nil {
403 drawX = lf
404 for _, y := range g.yTicks {
405
406 str = g.YTickStr(y, g.yPrecision)
407 strWd = pdf.GetStringWidth(str)
408 if g.YLabelIn {
409 pdf.SetXY(drawX+strOfs, g.Y(y)-halfTextSz)
410 } else {
411 pdf.SetXY(lf-strOfs-strWd, g.Y(y)-halfTextSz)
412 }
413 pdf.CellFormat(strWd, textSz, str, "", 0, "L", true, 0, "")
414 }
415 }
416
417
418 st.Put(pdf)
419
420 }
421
422 }
423
424
425
426
427 func (g GridType) Plot(pdf *Fpdf, xMin, xMax float64, count int, fnc func(x float64) (y float64)) {
428 if count > 0 {
429 var x, delta, drawX0, drawY0, drawX1, drawY1 float64
430 delta = (xMax - xMin) / float64(count)
431 x = xMin
432 for j := 0; j <= count; j++ {
433 if j == 0 {
434 drawX1 = g.X(x)
435 drawY1 = g.Y(fnc(x))
436 } else {
437 pdf.Line(drawX0, drawY0, drawX1, drawY1)
438 }
439 x += delta
440 drawX0 = drawX1
441 drawY0 = drawY1
442 drawX1 = g.X(x)
443 drawY1 = g.Y(fnc(x))
444 }
445 }
446 }
447
View as plain text