1package gofpdf
2
3import (
4	"math"
5	"strconv"
6)
7
8func unused(args ...interface{}) {
9}
10
11// RGBType holds fields for red, green and blue color components (0..255)
12type RGBType struct {
13	R, G, B int
14}
15
16// RGBAType holds fields for red, green and blue color components (0..255) and
17// an alpha transparency value (0..1)
18type RGBAType struct {
19	R, G, B int
20	Alpha   float64
21}
22
23// StateType holds various commonly used drawing values for convenient
24// retrieval (StateGet()) and restore (Put) methods.
25type 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// StateGet returns a variable that contains common state values.
35func 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// Put sets the common state values contained in the state structure
47// specified by st.
48func (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// TickFormatFncType defines a callback for label drawing.
59type TickFormatFncType func(val float64, precision int) string
60
61// defaultFormatter returns the string form of val with precision decimal places.
62func defaultFormatter(val float64, precision int) string {
63	return strconv.FormatFloat(val, 'f', precision, 64)
64}
65
66// GridType assists with the generation of graphs. It allows the application to
67// work with logical data coordinates rather than page coordinates and assists
68// with the drawing of a background grid.
69type GridType struct {
70	// Chart coordinates in page units
71	x, y, w, h float64
72	// X, Y, Wd, Ht float64
73	// Slopes and intercepts scale data points to graph coordinates linearly
74	xm, xb, ym, yb float64
75	// Tickmarks
76	xTicks, yTicks []float64
77	// Labels are inside of graph boundary
78	XLabelIn, YLabelIn bool
79	// Labels on X-axis should be rotated
80	XLabelRotate bool
81	// Formatters; use nil to eliminate labels
82	XTickStr, YTickStr TickFormatFncType
83	// Subdivisions between tickmarks
84	XDiv, YDiv int
85	// Formatting precision
86	xPrecision, yPrecision int
87	// Line and label colors
88	ClrText, ClrMain, ClrSub RGBAType
89	// Line thickness
90	WdMain, WdSub float64
91	// Label height in points
92	TextSize float64
93}
94
95// linear returns the slope and y-intercept of the straight line joining the
96// two specified points. For scaling purposes, associate the arguments as
97// follows: x1: observed low value, y1: desired low value, x2: observed high
98// value, y2: desired high value.
99func 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// linearTickmark returns the slope and intercept that will linearly map data
108// values (the range of which is specified by the tickmark slice tm) to page
109// values (the range of which is specified by lo and hi).
110func 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// NewGrid returns a variable of type GridType that is initialized to draw on a
119// rectangle of width w and height h with the upper left corner positioned at
120// point (x, y). The coordinates are in page units, that is, the same as those
121// specified in New().
122//
123// The returned variable is initialized with a very simple default tickmark
124// layout that ranges from 0 to 1 in both axes. Prior to calling Grid(), the
125// application may establish a more suitable tickmark layout by calling the
126// methods TickmarksContainX() and TickmarksContainY(). These methods bound the
127// data range with appropriate boundaries and divisions. Alternatively, if the
128// exact extent and divisions of the tickmark layout are known, the methods
129// TickmarksExtentX() and TickmarksExtentY may be called instead.
130func 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 // Points
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// WdAbs returns the absolute value of dataWd, specified in logical data units,
154// that has been converted to the unit of measure specified in New().
155func (g GridType) WdAbs(dataWd float64) float64 {
156	return math.Abs(g.xm * dataWd)
157}
158
159// Wd converts dataWd, specified in logical data units, to the unit of measure
160// specified in New().
161func (g GridType) Wd(dataWd float64) float64 {
162	return g.xm * dataWd
163}
164
165// XY converts dataX and dataY, specified in logical data units, to the X and Y
166// position on the current page.
167func (g GridType) XY(dataX, dataY float64) (x, y float64) {
168	return g.xm*dataX + g.xb, g.ym*dataY + g.yb
169}
170
171// Pos returns the point, in page units, indicated by the relative positions
172// xRel and yRel. These are values between 0 and 1. xRel specifies the relative
173// position between the grid's left and right edges. yRel specifies the
174// relative position between the grid's bottom and top edges.
175func (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// X converts dataX, specified in logical data units, to the X position on the
182// current page.
183func (g GridType) X(dataX float64) float64 {
184	return g.xm*dataX + g.xb
185}
186
187// HtAbs returns the absolute value of dataHt, specified in logical data units,
188// that has been converted to the unit of measure specified in New().
189func (g GridType) HtAbs(dataHt float64) float64 {
190	return math.Abs(g.ym * dataHt)
191}
192
193// Ht converts dataHt, specified in logical data units, to the unit of measure
194// specified in New().
195func (g GridType) Ht(dataHt float64) float64 {
196	return g.ym * dataHt
197}
198
199// Y converts dataY, specified in logical data units, to the Y position on the
200// current page.
201func (g GridType) Y(dataY float64) float64 {
202	return g.ym*dataY + g.yb
203}
204
205// XRange returns the minimum and maximum values for the current tickmark
206// sequence. These correspond to the data values of the graph's left and right
207// edges.
208func (g GridType) XRange() (min, max float64) {
209	min = g.xTicks[0]
210	max = g.xTicks[len(g.xTicks)-1]
211	return
212}
213
214// YRange returns the minimum and maximum values for the current tickmark
215// sequence. These correspond to the data values of the graph's bottom and top
216// edges.
217func (g GridType) YRange() (min, max float64) {
218	min = g.yTicks[0]
219	max = g.yTicks[len(g.yTicks)-1]
220	return
221}
222
223// TickmarksContainX sets the tickmarks to be shown by Grid() in the horizontal
224// dimension. The argument min and max specify the minimum and maximum values
225// to be contained within the grid. The tickmark values that are generated are
226// suitable for general purpose graphs.
227//
228// See TickmarkExtentX() for an alternative to this method to be used when the
229// exact values of the tickmarks are to be set by the application.
230func (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// TickmarksContainY sets the tickmarks to be shown by Grid() in the vertical
236// dimension. The argument min and max specify the minimum and maximum values
237// to be contained within the grid. The tickmark values that are generated are
238// suitable for general purpose graphs.
239//
240// See TickmarkExtentY() for an alternative to this method to be used when the
241// exact values of the tickmarks are to be set by the application.
242func (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
247func 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// TickmarksExtentX sets the tickmarks to be shown by Grid() in the horizontal
258// dimension. count specifies number of major tickmark subdivisions to be
259// graphed. min specifies the leftmost data value. div specifies, in data
260// units, the extent of each major tickmark subdivision.
261//
262// See TickmarkContainX() for an alternative to this method to be used when
263// viewer-friendly tickmarks are to be determined automatically.
264func (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// TickmarksExtentY sets the tickmarks to be shown by Grid() in the vertical
270// dimension. count specifies number of major tickmark subdivisions to be
271// graphed. min specifies the bottommost data value. div specifies, in data
272// units, the extent of each major tickmark subdivision.
273//
274// See TickmarkContainY() for an alternative to this method to be used when
275// viewer-friendly tickmarks are to be determined automatically.
276func (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// func (g *GridType) SetXExtent(dataLf, paperLf, dataRt, paperRt float64) {
282// 	g.xm, g.xb = linear(dataLf, paperLf, dataRt, paperRt)
283// }
284
285// func (g *GridType) SetYExtent(dataTp, paperTp, dataBt, paperBt float64) {
286// 	g.ym, g.yb = linear(dataTp, paperTp, dataBt, paperBt)
287// }
288
289func 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// Grid generates a graph-paperlike set of grid lines on the current page.
296func (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		// Verticals along X axis
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		// Horizontals along Y axis
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		// X labels
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		// Y labels
402		if g.YTickStr != nil {
403			drawX = lf
404			for _, y := range g.yTicks {
405				// str = strconv.FormatFloat(y, 'f', g.yPrecision, 64)
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		// Restore drawing attributes
418		st.Put(pdf)
419
420	}
421
422}
423
424// Plot plots a series of count line segments from xMin to xMax. It repeatedly
425// calls fnc(x) to retrieve the y value associate with x. The currently
426// selected line drawing attributes are used.
427func (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