Another graph drawing using vector library

Share your advanced PureBasic knowledge/code with the community.
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

Another graph drawing using vector library

Post by Lunasole »

Hi once again ^^

Here is some stuff I've made this night for one program used by myself. And as many of my stuff, this one is also quickly/dirty made and other such things :)

But well, I'm too lazy to describe it nicely this time [going to play C&C campaign instead].
Should just say: following code draws simple resolution-independent graph in two modes: raw values + optionally, averaged by specified amount of data.
There is only one function, it takes PB image and draws graph on it.

For anything else go and play with code if you like it

Code: Select all

EnableExplicit

;{ Simple Graph }

	; Yet another stuff for simple 'visuals'
	;	2017			(c) Luna Sole
	; 	v1.0.0.4 		(+ mouse interaction)			
	

	; Draws graph with 2 lines on given image using PB vector library
	; hImgOut				PB image to draw on it, can be any size [except maybe "extremely low" resolutions ^^]
	; GraphData()			array with items to visualise, should have at least 1 item [starting from 0]
	; OutGeoData()			map to receive data required to handle mouse interaction. map key = X coordinate on graph image, value = related index of GraphData()
	; Average				how many items use to calculate averaged value for current item? 2 is minimum, if less it will be disabled
	; GridStepX				X grid resolution [vertical lines], measured in GraphData() count. use 0 to disable
	; GridStepY				Y grid resolution [horizontal lines], measured in GraphData() values. 0 to disable
	; FontSize				font size of text labels. use 0 to disable labels
	; ColorN				colors for graph elements (RGB)
	; RETURN:				none, image modified on success
	Procedure DrawSimpleGraph (hImgOut, Array GraphData(1), Map OutGeoData(), Average, GridStepX = 10, GridStepY = 10, FontSize = 9, Color1 = $00DDDD, Color2 = $DDDD00)
		ClearMap(OutGeoData())						; reset mouse data
		
		Protected maxItems = ArraySize(GraphData())	; count of items  (X)
		Protected maxValues							; count of values (Y)
		Protected highValue, lowValue				; highest and lowest GraphData values
		Protected t									; generic temp variable
		If maxItems < 0 : ProcedureReturn :	EndIf	; quit if GraphData array is invalid

		; detect higher/lower values
		Protected Dim TData(0)
		CopyArray(GraphData(), TData())
		SortArray(TData(), #PB_Sort_Ascending)
		lowValue = TData(0)
		highValue = TData(maxItems)
		FreeArray(TData())
		
		; do some stuff to better fit background grid
		If GridStepY > 0
			If highValue >= 0
				highValue + GridStepY - highValue % GridStepY
			ElseIf highValue
				highValue - highValue % GridStepY
			EndIf
			If lowValue > 0
				lowValue  - lowValue  % GridStepY
			ElseIf lowValue < 0
				lowValue  - (GridStepY + lowValue  % GridStepY)
			EndIf
		EndIf
		maxValues = highValue - lowValue
		If maxValues <= 0 
			maxValues = 1
		EndIf
		; load font for text labels
		Protected Font 
		If FontSize > 0
			Font = LoadFont(#PB_Any, "arial", FontSize)
			If Not IsFont(Font) ; and so on
				Font = LoadFont(#PB_Any, "tahoma", FontSize)
				If Not IsFont(Font) ; and so on.. :)
					Font = LoadFont(#PB_Any, "consolas", FontSize)
				EndIf
			EndIf
		EndIf
		
		; draw data to image
		If StartVectorDrawing(ImageVectorOutput(hImgOut, #PB_Unit_Pixel))
			; [n] - define graph offsets / text labels sizes / etc
			Protected.d oX = 1.0, oY = oX
			Protected.d mtW = 1.0, mtH = mtW
			If IsFont(Font)
				VectorFont(FontID(Font), FontSize)
				If VectorTextWidth(Str(highValue)) > VectorTextWidth(Str(lowValue))
					oX = VectorTextWidth(Str(highValue)) + 2.0
				Else
					oX = VectorTextWidth(Str(lowValue)) + 2.0
				EndIf
				oY = VectorTextHeight("0A") + 2.0
				If oX > oY
					oY = oX
				Else
					oX = oY
				EndIf
				mtW = VectorTextWidth(Str(maxItems)) * 1.2
				mtH = VectorTextHeight(Str(highValue)) * 0.8
			EndIf
			; 	line width (pixels)
			Protected.d LineWidth = 1.0
			; 	multipliers to scale graph coordinates
			Protected.d mX = (VectorOutputWidth() - oX * 2.0) / (maxItems + Bool(maxItems = 0))	
			Protected.d mY = (VectorOutputHeight() - oY * 2.0) / maxValues
			;	tmp variables used in drawing
			Protected.d cX, cY
			
			; [0] - draw text labels and grid lines
			VectorSourceColor($77FFFFFF)
			Protected.d tLast
			;	horizontal lines/labels
			tLast = oY + maxValues * mY + 5.0
			If GridStepY > 0
				For t = maxValues To 0 Step -1
					If t % GridStepY = 0 Or t = maxValues
						cY = oY + t * mY
						; draw text label
						If IsFont(Font) And cY < tLast
							MovePathCursor(oX - (2.0 + VectorTextWidth(Str(highValue - t))), cY - VectorTextHeight(Str(highValue - t)) * 0.5)
							DrawVectorText(Str(highValue - t))
							tLast = cY - mtH
						EndIf
						; draw grid line
						MovePathCursor(oX, oY + mY * t)				
						AddPathLine(oX + maxItems * mX, oY + mY * t)
					EndIf
				Next t
			EndIf
			;	vertical lines/labels
			tLast = 0.0
			If GridStepX > 0	
				;GridStepX + 1
				For t = 0 To maxItems
					If t % GridStepX = 0 Or t = maxItems
						cX = oX + t * mX
						cY = oY + maxValues * mY
						; draw text label
						If IsFont(Font) And cX > tLast
							MovePathCursor(cX - VectorTextWidth(Str(t)) * 0.5, cY + 2.0)
							DrawVectorText(Str(t))
							tLast = cX + mtW
						EndIf
						; draw grid line
						MovePathCursor(cX, oY)
						AddPathLine(cX, cY)
					EndIf
				Next t
			EndIf
			;	fin
			If GridStepX > 0 Or GridStepY > 0
				DashPath(1.0, 3.0)
			EndIf
			
			
			; [1] - draw main line/items and form "geodata"
			Protected.d gX = oX
			Protected.d gX2
			VectorSourceColor(Color1 | $FF000000)
			MovePathCursor(oX, oY + mY * (highValue - GraphData(0)))
			For t = 0 To maxItems
				cX = oX + mX * t
				cY = oY + mY * (highValue - GraphData(t))
				AddPathLine(cX, cY)			; add line
				AddPathCircle(cX, cY, 2.0)	; add spot at the edge
				MovePathCursor(cX, cY)		; restore cursor pos
				
				; build that "geodata" used to handle mouse
				gX2 = gX + (cX - gX) * 0.5
				While gX < gX2
					OutGeoData(Str(Int(gX))) = t - 1
					gX + 1.0
				Wend
				While gX < cX
					OutGeoData(Str(Int(gX))) = t
					gX + 1.0
				Wend
				gX = cX
			Next t
			StrokePath(LineWidth)
			
			
			; [2] - draw "trend"/averaged line
			Protected NewList Avg() ; stack to store recent values
			Protected.d Avg			; to calculate current result
			If Average > maxItems + 1
				Average = maxItems + 1
			EndIf
			If Average > 1				; it makes sense to draw only if it is greater than 1
				While ListSize(Avg()) < Average
					AddElement(Avg())	; 'extrapolate' using first item data, without that it looks worst as for me :3
					Avg() = GraphData(0)
				Wend
				MovePathCursor(oX, oY + (highValue - GraphData(0)) * mY)
				For t = 0 To maxItems
					FirstElement(Avg())		; this all works like a stack structure, with max deepth = Average
					DeleteElement(Avg())	
					LastElement(Avg())		
					AddElement(Avg())
					Avg() = GraphData(t)	; push new element
					Avg = 0.0				; calculate averaged value from recent elements
					ForEach Avg()
						Avg + Avg()
					Next
					cX = oX + mX * t
					cY = oY + (highValue - Avg / Average) * mY
					AddPathLine(cX, cY)
					AddPathCircle(cX, cY, 1.0)	; add spot at the edge
					MovePathCursor(cX, cY)		; restore cursor pos
				Next t
				VectorSourceColor(Color2 | $FF000000)
				StrokePath(LineWidth)
			EndIf
			
			; cls
			FreeList(Avg())
			StopVectorDrawing()
		EndIf
		
		; cls
		If IsFont(Font)
			FreeFont(Font)
		EndIf
	EndProcedure
	
;}


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Test / Example
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; used to handle graph mouse clicks
; mapkey is X coordinate of graph image, value is corresponding index in GraphData() array
Global NewMap GraphGeodata()

; Graph dimensions
Global W = 800, H = 600
; Image to draw graph
Global tImg
; Window and canvas to display graph
Global tWindow, tCanvas


; This procedure handles mouse events of graph canvas
Procedure GraphCB()
	Protected cX = GetGadgetAttribute(tCanvas, #PB_Canvas_MouseX)
	Select EventType()
		Case #PB_EventType_LeftButtonDown:
			If FindMapElement(GraphGeodata(), Str(cX))
				Debug "LMB down on item " + GraphGeodata()
			EndIf
		Case #PB_EventType_LeftButtonUp:
			If FindMapElement(GraphGeodata(), Str(cX))
				Debug "LMB up on item " + GraphGeodata()
			EndIf
		Case #PB_EventType_LeftClick:
			If FindMapElement(GraphGeodata(), Str(cX))
				Debug "LMB click on item " + GraphGeodata()
			EndIf
			
		Case #PB_EventType_RightButtonDown:
			If FindMapElement(GraphGeodata(), Str(cX))
				Debug "RMB down on item " + GraphGeodata()
			EndIf
		Case #PB_EventType_RightButtonUp:
			If FindMapElement(GraphGeodata(), Str(cX))
				Debug "RMB up on item " + GraphGeodata()
			EndIf
		Case #PB_EventType_RightClick:
			If FindMapElement(GraphGeodata(), Str(cX))
				Debug "RMB click on item " + GraphGeodata()
			EndIf
			
		Case #PB_EventType_MouseMove:
			If FindMapElement(GraphGeodata(), Str(cX))
				Debug "Mouse hover on item " + GraphGeodata()
			EndIf
	EndSelect
EndProcedure

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; generate some graph data
Dim GraphValues (127)
Define t
RandomSeed('LS')
For t = 0 To ArraySize(GraphValues())
	GraphValues(t) = Random(127, 0)
	If Random(1, 0)
		GraphValues(t) = -GraphValues(t)
	EndIf
Next t

; create image to receive output
tImg = CreateImage(#PB_Any, W, H, 32, $405050)
; draw graph to created image
DrawSimpleGraph(tImg, GraphValues(), GraphGeodata(), 16, 10, 10, 11)

; show
tWindow = OpenWindow(#PB_Any, 0, 0, W, H, "SomeGraphWindow", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
tCanvas = CanvasGadget(#PB_Any, 0, 0, W, H)
SetGadgetAttribute(tCanvas, #PB_Canvas_Image, ImageID(tImg))
; bind events required for mouse interaction
BindEvent(#PB_Event_Gadget, @GraphCB(), tWindow, tCanvas, #PB_All)

; release img
FreeImage(tImg)

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
Last edited by Lunasole on Sat Jun 24, 2017 10:23 pm, edited 8 times in total.
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
User avatar
Keya
Addict
Addict
Posts: 1890
Joined: Thu Jun 04, 2015 7:10 am

Re: Another graph drawing using vector library

Post by Keya »

Really nice! Especially how it scales so flexibly
Image
Image
collectordave
Addict
Addict
Posts: 1310
Joined: Fri Aug 28, 2015 6:10 pm
Location: Portugal

Re: Another graph drawing using vector library

Post by collectordave »

Lovely bit of code

I will probably use that somewhere.

Regards

cd
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Another graph drawing using vector library

Post by davido »

@Lunasole,

Very nice, thank you for sharing.
DE AA EB
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

Re: Another graph drawing using vector library

Post by Lunasole »

Thanks for replies (and additionally thank Keya for nice screenshots ^^).

There was a bug at line 57 BTW, just updated code in first post.
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
User avatar
Zebuddi123
Enthusiast
Enthusiast
Posts: 796
Joined: Wed Feb 01, 2012 3:30 pm
Location: Nottinghamshire UK
Contact:

Re: Another graph drawing using vector library

Post by Zebuddi123 »

Hi Lunasole thanks for sharing. :) I can use this for a project I`m working on but I need the graph to be resizeable which I have done. Now the graph is redrawn on #PB_Event_SizeWindow, only trouble is the flicker on the plus window resize. Any idea how to solve this ? CanvasGadget and stuff is not something i have played with up to now.

Updated: as the image is recreated on window resize and needs the new windows co-ordinates before recreating the img, think this is best

Zebuddi. :)

Code: Select all

;{ Simple Graph }

; Yet another stuff for simple 'visuals'
;   2017         (c) Luna Sole
;    v1.0.0.1         

; Draws graph with 2 lines on given image using PB vector library
; hImgOut            PB image to draw on it, can be any size [except maybe "extremely low" resolutions ^^]
; GraphData()         array with items to visualise
; Average            how many items use to calculate averaged value for current item? 2 is minimum, if less it will be disabled
; GridStepX            X grid resolution [vertical lines], measured in GraphData() count. use 0 to disable
; GridStepY            Y grid resolution [horizontal lines], measured in GraphData() values. 0 to disable
; FontSize            font size of text labels. use 0 to disable labels
; ColorN            colors for graph elements (RGB)
; RETURN:            none, image modified on success

DeclareModule SimpleVectorGraph
	Declare DrawSimpleGraph (hWinID, Array GraphData(1), Average, GridStepX = 10, GridStepY = 10, FontSize = 9, Color1 = $00DDDD, Color2 = $DDDD00)	
EndDeclareModule

Module SimpleVectorGraph
	Procedure DrawSimpleGraph (hWinID, Array GraphData(1), Average, GridStepX = 10, GridStepY = 10, FontSize = 9, Color1 = $00DDDD, Color2 = $DDDD00)
		Protected tImg = CreateImage(#PB_Any, WindowWidth(hWinID) , WindowHeight(hWinID), 32, $405050)
		Protected maxItems = ArraySize(GraphData())   ; count of values (defines width)
		Protected highValue = 0, lowValue = $FFFFFFF  ; highest and lowest GraphData values
		Protected maxValues = 0
		Protected t                           ; generic temp variable
		ResizeGadget(0, 0, 0, WindowWidth(hWinID), WindowHeight(hWinID))
		; detect higher/lower values
		For t = 0 To maxItems
			If GraphData(t) > highValue
				highValue = GraphData(t)
			EndIf
			If GraphData(t) < lowValue
				lowValue = GraphData(t)
			EndIf
		Next t
		; do some stuff to better fit background grid
		If GridStepY > 0
			highValue + GridStepY - highValue % GridStepY
			lowValue  - lowValue  % GridStepY
		EndIf
		maxValues = highValue - lowValue
		
		; load font for text labels
		Protected Font 
		If FontSize > 0
			Font = LoadFont(#PB_Any, "arial", FontSize)
			If Not IsFont(Font) ; and so on
				Font = LoadFont(#PB_Any, "tahoma", FontSize)
				If Not IsFont(Font) ; and so on.. :)
					Font = LoadFont(#PB_Any, "consolas", FontSize)
				EndIf
			EndIf
		EndIf
		
		; draw data to image
		If StartVectorDrawing(ImageVectorOutput(tImg, #PB_Unit_Pixel))
			
			; define text labels size / graph offsets
			Protected.d tW = 1.0, tH = tW
			If IsFont(Font)
				VectorFont(FontID(Font), FontSize)
				tW = 2.0 + VectorTextWidth(Str(maxItems))
				tH = tW
			EndIf
			
			Protected.d LineWidth = 1.0                              ; line width (pixels)
			Protected.d mX = (VectorOutputWidth() - tW * 2.0) / maxItems   ; multipliers to scale graph coordinates
			Protected.d mY = (VectorOutputHeight() - tH * 2.0) / maxValues
			
			; 0 - draw text labels and grid lines
			VectorSourceColor($77FFFFFF)
			; draw horizontal grid lines
			Protected.d lastH = tH + maxValues * mY + 5.0
			If GridStepY > 0
				For t = maxValues To 0 Step -1
					If t % GridStepY = 0 Or t = maxValues
						; draw text label
						If IsFont(Font) And tH + t * mY < lastH
							MovePathCursor(tW - (2.0 + VectorTextWidth(Str(highValue - t))), tH + mY * t - VectorTextHeight(Str(highValue - t)) * 0.5)
							DrawVectorText(Str(highValue - t))
							; store offset of next element
							lastH = tH + t * mY - tH * 0.8
						EndIf
						; draw grid line
						MovePathCursor(tW, tH + mY * t)            
						AddPathLine(tW + maxItems * mX, tH + mY * t)
					EndIf
				Next t
			EndIf
			
			; draw vertical grid lines
			Protected.d lastV
			If GridStepX > 0
				For t = 0 To maxItems
					If t % GridStepX = 0 Or t = maxItems
						; draw text label
						If IsFont(Font) And tW + t * mX > lastV
							MovePathCursor(tW + t * mX - VectorTextWidth(Str(t)) * 0.5, tH + maxValues * mY + 2.0)
							DrawVectorText(Str(t))
							; store offset of next element
							lastV = tW + t * mX + tW * 1.2
						EndIf
						; draw grid line
						MovePathCursor(tW + t * mX, tH)
						AddPathLine(tW + t * mX, tH + maxValues * mY)
					EndIf
				Next t
			EndIf
			If GridStepX > 0 Or GridStepY > 0
				DashPath(1.0, 3.0)
			EndIf
			
			
			; 1 - draw main line
			MovePathCursor(tW, tH + mY * (highValue - GraphData(0)))
			For t = 0 To maxItems
				AddPathLine(tW + mX * t, tH + mY * (highValue - GraphData(t)))
				
				AddPathCircle(tW + mX * t, tH + mY * (highValue - GraphData(t)), 2.0); add spot at the edge
				MovePathCursor(tW + mX * t, tH + mY * (highValue - GraphData(t)))	   ; restore cursor pos
			Next t
			VectorSourceColor(Color1 | $FF000000)
			StrokePath(LineWidth)
			
			; 2 - draw "trend"/averaged line
			Protected NewList Avg() ; stack to store recent values
			Protected.d Avg		; to calculate current result
			If Average > maxItems + 1
				Average = maxItems + 1
			EndIf
			If Average > 1         ; it makes sense to draw only if it is greater than 1
				While ListSize(Avg()) < Average
					AddElement(Avg())   ; fill stack with first item data, to avoid incorrect results on start
					Avg() = GraphData(0)
				Wend
				MovePathCursor(tW, tH + (highValue - GraphData(0)) * mY)
				For t = 0 To maxItems
					FirstElement(Avg())      ; this all works like a stack structure, with max deepth = Average
					DeleteElement(Avg())   
					LastElement(Avg())      
					AddElement(Avg())
					Avg() = GraphData(t)   ; push new element
					
					Avg = 0.0            ; calculate averaged value from recent elements
					ForEach Avg()
						Avg + Avg()
					Next
					Avg = Avg / Average
					AddPathLine(tW + mX * t, tH + (highValue - Avg) * mY)
					
					AddPathCircle(tW + mX * t, tH + (highValue - Avg) * mY, 1.0)   ; add spot at the edge
					MovePathCursor(tW + mX * t, tH + (highValue - Avg) * mY)	   ; restore cursor pos
				Next t
				VectorSourceColor(Color2 | $FF000000)
				StrokePath(LineWidth)
			EndIf
			
			; cls
			If IsFont(Font)
				FreeFont(Font)
			EndIf
			FreeList(Avg())
			StopVectorDrawing()
		EndIf
		SetGadgetAttribute(0, #PB_Canvas_Image, ImageID(tImg))
		FreeImage(tImg)
	EndProcedure
EndModule
;}

Define W = 800
Define H = 600


; define graph data
Global Dim GraphValues (127)
Define t
RandomSeed('LS')
For t = 0 To ArraySize(GraphValues())
	GraphValues(t) = Random(295, 45)
Next t

; Test / Example
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; draw graph to created image
UseModule SimpleVectorGraph
	
	; show
	If OpenWindow(0, 0, 0, W, H, "SomeGraphWindow", #PB_Window_SystemMenu | #PB_Window_ScreenCentered|#PB_Window_SizeGadget) And CanvasGadget(0, 0, 0, W, H)
		SimpleVectorGraph::DrawSimpleGraph(0, GraphValues(), 16, 50, 50, 15)	
		Repeat
			ev = WaitWindowEvent()
			Select ev
				Case #PB_Event_SizeWindow
					SimpleVectorGraph::DrawSimpleGraph(0, GraphValues(), 16, 50, 50, 15)
			EndSelect
		Until  ev = #PB_Event_CloseWindow
	EndIf
Last edited by Zebuddi123 on Sat Jan 14, 2017 3:49 pm, edited 2 times in total.
malleo, caput, bang. Ego, comprehendunt in tempore
User avatar
Keya
Addict
Addict
Posts: 1890
Joined: Thu Jun 04, 2015 7:10 am

Re: Another graph drawing using vector library

Post by Keya »

Zebuddi123 hrmm im not really experiencing any flicker on XP, and youre already using BindEvent to capture the resize which wouldve been my first suggestion anyway hehe. Nice addition btw!
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

Re: Another graph drawing using vector library

Post by Lunasole »

Zebuddi123 wrote:Hi Lunasole thanks for sharing. :) I can use this for a project I`m working on but I need the graph to be resizeable which I have done. Now the graph is redrawn on #PB_Event_SizeWindow, only trouble is the flicker on the plus window resize. Any idea how to solve this ? CanvasGadget and stuff is not something i have played with up to now.
Hi, nice idea with resizable window ^^ It compensates cases where some labels may not fit nicely.
As Keya I also didn't seen any flickering, but maybe you getting that problem because of calling ResizeWindow() from your callback (which is not necessary):

Code: Select all

Procedure myResizeWindow()
   Protected s= ElapsedMilliseconds()
;    ResizeWindow(0, #PB_Ignore, #PB_Ignore, WindowWidth(0), WindowHeight(0))   
   SimpleVectorGraph::DrawSimpleGraph(0, GraphValues(), 16, 10, 10, 11)
EndProcedure
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

Re: Another graph drawing using vector library

Post by Lunasole »

Updated post to v1.0.0.2 with several fixes/improvements (mainly about text labels positioning and overall "stability")
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
User avatar
Zebuddi123
Enthusiast
Enthusiast
Posts: 796
Joined: Wed Feb 01, 2012 3:30 pm
Location: Nottinghamshire UK
Contact:

Re: Another graph drawing using vector library

Post by Zebuddi123 »

Hi to all Have made a Module of LunaSole`s orignal code. Which is now modified for my use :) but maybe of use to others :)

graph window is resizeable and graph redrawn hopefully now without flicker

Pass all data to the module via a *structured variable with one call

ie:

Code: Select all

	UseModule SimpleVectorGraph ; must be called to make *GraphData available and initialized

		With *GraphData
			ReDim \GraphData(127)
			CopyArray(GraphValues(), \GraphData())
			\windowTitle		= "SomeGraphWindow"
			\windowFlags 	        = #PB_Window_SystemMenu | #PB_Window_ScreenCentered|#PB_Window_SizeGadget
			\hWinID 			= 0 ; a possitive integer
			\winX			 = 0
			\winY				= 0 
			\winWidth			= 800
			\winHeight		        = 600
			;\GraphData() 	        = GraphValues() 
			\Average 			= 16
			\GridStepX 		= 18
			\GridStepY 		        = 18
			\FontSize 			= 12
			\Color1			= $00DDDD
			\Color2			= $DDDD00
		EndWith	
		
		SimpleVectorGraph::OpenSimpleGraphWindow(*GraphData)


Code: Select all

;{ Simple Graph }

; Yet another stuff for simple 'visuals'
;   2017         (c) Luna Sole
;    v1.0.0.1         


;------------------------------------------------------------------------------
;Thanks To Luna Sole for the original code  :)
;
;Module version  by zebuddi123. 14/1/2017

;--------------------------------------------------------------------------------

DeclareModule SimpleVectorGraph
	EnableExplicit
	Structure SimpleGraphData
		hWinID.i								;handle to  window
		winX.i									; x co-ordinate
		winY.i									; y co-ordinate
		winWidth.i							;window width
		winHeight.i							;window height
		windowTitle.s						; window title
		windowFlags.i						; window flags
		Array GraphData.i(1)				;array with items to visualise/plot
		Average.i								;avarage
		GridStepX.i							;X grid resolution [vertical lines], measured in GraphData() count. use 0 to disable
		GridStepY.i							;Y grid resolution [horizontal lines], measured in GraphData() values. 0 to disable
		FontSize.i								;font size of text labels. use 0 to disable labels
		Color1.i									;colors for graph elements (RGB)
		Color2.i									; colors for graph elements (RGB)
	EndStructure
	
	Define *GraphData.SimpleGraphData = AllocateMemory(SizeOf(SimpleGraphData))   	; define pointer to structured variable and allocate memory
	InitializeStructure(*GraphData, SimpleGraphData)												 		; initailize tnhhe structured memory area
	
	Declare DrawSimpleGraph (*GraphData.SimpleGraphData)	
	Declare OpenSimpleGraphWindow(*GraphData.SimpleGraphData)
EndDeclareModule

Module SimpleVectorGraph
	
	Procedure  OpenSimpleGraphWindow(*GraphData.SimpleGraphData)
		Protected ev
		With *GraphData
			If OpenWindow(\hWinID, \winX, \winY, \winWidth, \winHeight, \windowTitle , \windowFlags ) And CanvasGadget(0, 0, 0, \winWidth, \winHeight)
				SetWindowColor(\hWinID, $405050)
				
				SimpleVectorGraph::DrawSimpleGraph(*GraphData )
				
				Repeat
					ev = WaitWindowEvent()
					Select ev
						Case #PB_Event_SizeWindow
							SimpleVectorGraph::DrawSimpleGraph(*GraphData)
					EndSelect
				Until  ev = #PB_Event_CloseWindow

				FreeArray(*GraphData\GraphData())
			EndIf
		EndWith
	EndProcedure
	
	
	Procedure DrawSimpleGraph (*GraphData.SimpleGraphData)
		Protected gr
		With *GraphData
			Protected tImg = CreateImage(#PB_Any, WindowWidth(\hWinID) , WindowHeight(\hWinID), 32, $405050)
			Protected maxItems = ArraySize(\GraphData())   ; count of values (defines width)
			Protected highValue = 0, lowValue = $FFFFFFF   ; highest and lowest GraphData values
			Protected maxValues = 0
			Protected t                           ; generic temp variable

			; detect higher/lower values
			For t = 0 To maxItems
				If \GraphData(t) > highValue
					highValue = \GraphData(t)
				EndIf
				If \GraphData(t) < lowValue
					lowValue = \GraphData(t)
				EndIf
			Next t
			; do some stuff to better fit background grid
			If \GridStepY > 0
				highValue + \GridStepY - highValue % \GridStepY
				lowValue  - lowValue  % \GridStepY
			EndIf
			maxValues = highValue - lowValue
			
			; load font for text labels
			Protected Font 
			If \FontSize > 0
				Font = LoadFont(#PB_Any, "arial", \FontSize)
				If Not IsFont(Font) ; and so on
					Font = LoadFont(#PB_Any, "tahoma", \FontSize)
					If Not IsFont(Font) ; and so on.. :)
						Font = LoadFont(#PB_Any, "consolas", \FontSize)
					EndIf
				EndIf
			EndIf
			
			; draw data to image
			If StartVectorDrawing(ImageVectorOutput(tImg, #PB_Unit_Pixel))
				
				; define text labels size / graph offsets
				Protected.d tW = 1.0, tH = tW
				If IsFont(Font)
					VectorFont(FontID(Font), \FontSize)
					tW = 2.0 + VectorTextWidth(Str(maxItems))
					tH = tW
				EndIf
				
				Protected.d LineWidth = 1.0                              ; line width (pixels)
				Protected.d mX = (VectorOutputWidth() - tW * 2.0) / maxItems   ; multipliers to scale graph coordinates
				Protected.d mY = (VectorOutputHeight() - tH * 2.0) / maxValues
				
				; 0 - draw text labels and grid lines
				VectorSourceColor($77FFFFFF)
				; draw horizontal grid lines
				Protected.d lastH = tH + maxValues * mY + 5.0
				If \GridStepY > 0
					For t = maxValues To 0 Step -1
						If t % \GridStepY = 0 Or t = maxValues
							; draw text label
							If IsFont(Font) And tH + t * mY < lastH
								MovePathCursor(tW - (2.0 + VectorTextWidth(Str(highValue - t))), tH + mY * t - VectorTextHeight(Str(highValue - t)) * 0.5)
								DrawVectorText(Str(highValue - t))
								; store offset of next element
								lastH = tH + t * mY - tH * 0.8
							EndIf
							; draw grid line
							MovePathCursor(tW, tH + mY * t)            
							AddPathLine(tW + maxItems * mX, tH + mY * t)
						EndIf
					Next t
				EndIf
				
				; draw vertical grid lines
				Protected.d lastV
				If \GridStepX > 0
					For t = 0 To maxItems
						If t % \GridStepX = 0 Or t = maxItems
							; draw text label
							If IsFont(Font) And tW + t * mX > lastV
								MovePathCursor(tW + t * mX - VectorTextWidth(Str(t)) * 0.5, tH + maxValues * mY + 2.0)
								DrawVectorText(Str(t))
								; store offset of next element
								lastV = tW + t * mX + tW * 1.2
							EndIf
							; draw grid line
							MovePathCursor(tW + t * mX, tH)
							AddPathLine(tW + t * mX, tH + maxValues * mY)
						EndIf
					Next t
				EndIf
				If \GridStepX > 0 Or \GridStepY > 0
					DashPath(1.0, 3.0)
				EndIf
				
				
				; 1 - draw main line
				MovePathCursor(tW, tH + mY * (highValue - \GraphData(0)))
				For t = 0 To maxItems
					AddPathLine(tW + mX * t, tH + mY * (highValue - \GraphData(t)))
					
					AddPathCircle(tW + mX * t, tH + mY * (highValue - \GraphData(t)), 2.0); add spot at the edge
					MovePathCursor(tW + mX * t, tH + mY * (highValue - \GraphData(t)))    ; restore cursor pos
				Next t
				VectorSourceColor(\Color1 | $FF000000)
				StrokePath(LineWidth)
				
				; 2 - draw "trend"/averaged line
				Protected NewList Avg() ; stack to store recent values
				Protected.d Avg		; to calculate current result
				If \Average > maxItems + 1
					\Average = maxItems + 1
				EndIf
				If \Average > 1         ; it makes sense to draw only if it is greater than 1
					While ListSize(Avg()) < \Average
						AddElement(Avg())   ; fill stack with first item data, to avoid incorrect results on start
						Avg() = \GraphData(0)
					Wend
					MovePathCursor(tW, tH + (highValue - \GraphData(0)) * mY)
					For t = 0 To maxItems
						FirstElement(Avg())      ; this all works like a stack structure, with max deepth = \Average
						DeleteElement(Avg())   
						LastElement(Avg())      
						AddElement(Avg())
						Avg() = \GraphData(t)   ; push new element
						
						Avg = 0.0            ; calculate \Averaged value from recent elements
						ForEach Avg()
							Avg + Avg()
						Next
						Avg = Avg / \Average
						AddPathLine(tW + mX * t, tH + (highValue - Avg) * mY)
						
						AddPathCircle(tW + mX * t, tH + (highValue - Avg) * mY, 1.0)   ; add spot at the edge
						MovePathCursor(tW + mX * t, tH + (highValue - Avg) * mY)	   ; restore cursor pos
					Next t
					VectorSourceColor(\Color2 | $FF000000)
					StrokePath(LineWidth)
				EndIf
				
				; cls
				If IsFont(Font)
					FreeFont(Font)
				EndIf
				FreeList(Avg())
				StopVectorDrawing()
			EndIf
			ResizeGadget(\hWinID, 0, 0, WindowWidth(\hWinID), WindowHeight(\hWinID))
			SetGadgetAttribute(0, #PB_Canvas_Image, ImageID(tImg))
			FreeImage(tImg)
		EndWith
	EndProcedure
EndModule
;}


CompilerIf  #PB_Compiler_Debugger 
	
	; Test / Example
	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	
	
	DisableExplicit
	Define W = 800
	Define H = 600
	
	
	; define graph data
	Define t
	RandomSeed('LS')
	Dim GraphValues (127)	
	For t = 0 To ArraySize(GraphValues())
		GraphValues(t) = Random(295, 45)
	Next t
	
	; use of module -----------------------------------------------------------------------------
	UseModule SimpleVectorGraph ; must be called to make *GraphData to be initialized
				With *GraphData
			ReDim \GraphData(127)
			CopyArray(GraphValues(), \GraphData())
			\windowTitle		= "SomeGraphWindow"
			\windowFlags 	        = #PB_Window_SystemMenu | #PB_Window_ScreenCentered|#PB_Window_SizeGadget
			\hWinID 			= 0 ; a possitive integer
			\winX			= 0
			\winY				= 0 
			\winWidth			= 800
			\winHeight		        = 600
			;\GraphData() 	        = GraphValues() 
			\Average 			= 16
			\GridStepX 		= 18
			\GridStepY 		        = 18
			\FontSize 			= 12
			\Color1			= $00DDDD
			\Color2			= $DDDD00
		EndWith		
		
		SimpleVectorGraph::OpenSimpleGraphWindow(*GraphData)
		;-----------------------------------------------------------------------------------------------------------	
		FreeArray(GraphValues())
		End
	UnuseModule SimpleVectorGraph
CompilerEndIf	
malleo, caput, bang. Ego, comprehendunt in tempore
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

Re: Another graph drawing using vector library

Post by Lunasole »

Just wasted few minutes more on this stuff and probably can say "it looks finished" (well... at least for purposes I'm using it)
v1.0.0.3:
- now it supports both negative and positive graph values
- few calculation/speed/other optimizations

Updated code in a first post.


@Zebuddi123 thanks for your variant, although I'm rarely using modules (even in my largest code I didn't used, instead having fun to struggle all architecture problems manually ^^)
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
User avatar
Sicro
Enthusiast
Enthusiast
Posts: 559
Joined: Wed Jun 25, 2014 5:25 pm
Location: Germany
Contact:

Re: Another graph drawing using vector library

Post by Sicro »

Very nice, but the code doesn't work on my linux:
[10:24:18] Waiting for executable to start...
[10:24:18] Executable type: Linux - x64 (64bit, Unicode, Purifier)
[10:24:18] Executable started.
[10:24:18] [WARNING] Line: 219
[10:24:18] [WARNING] GdkPixbuf (CRITICAL): gdk_pixbuf_copy_area: assertion '!(gdk_pixbuf_get_has_alpha (src_pixbuf) && !gdk_pixbuf_get_has_alpha (dest_pixbuf))' failed
Image
Why OpenSource should have a license :: PB-CodeArchiv-Rebirth :: Pleasant-Dark (syntax color scheme) :: RegEx-Engine (compiles RegExes to NFA/DFA)
Manjaro Xfce x64 (Main system) :: Windows 10 Home (VirtualBox) :: Newest PureBasic version
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

Re: Another graph drawing using vector library

Post by Lunasole »

Sicro wrote:Very nice, but the code doesn't work on my linux
Sorry man, I didn't ever used this on linux, have no idea about those gdk problems on SetGadgetAttribute()
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
User avatar
Sicro
Enthusiast
Enthusiast
Posts: 559
Joined: Wed Jun 25, 2014 5:25 pm
Location: Germany
Contact:

Re: Another graph drawing using vector library

Post by Sicro »

This is a bug in PB, as I have just found out.
PB help wrote:CanvasGadget()
[...]
The SetGadgetAttribute() function can be used to modify the following gadget attributes

#PB_Canvas_Image Applies the given ImageID to the CanvasGadget. The gadget makes a copy of the input image so it can be freed or reused after this call. Setting this attribute is the same as using StartDrawing(), CanvasOutput() and DrawImage() to draw the image onto the CanvasGadget.
According to the help, the command "SetGadgetAttribute(0, #PB_Canvas_Image, ...)" should do exactly the same thing I do step by step in the following code:

Code: Select all

; show
If OpenWindow(0, 0, 0, W, H, "SomeGraphWindow", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) And CanvasGadget(0, 0, 0, W, H)
   ;SetGadgetAttribute(0, #PB_Canvas_Image, ImageID(tImg))
   If StartDrawing(CanvasOutput(0))
      DrawImage(ImageID(tImg), 0, 0)
      StopDrawing()
   EndIf
   FreeImage(tImg)
   
   Repeat
   Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
With this code it also works under Linux :)
Image
Why OpenSource should have a license :: PB-CodeArchiv-Rebirth :: Pleasant-Dark (syntax color scheme) :: RegEx-Engine (compiles RegExes to NFA/DFA)
Manjaro Xfce x64 (Main system) :: Windows 10 Home (VirtualBox) :: Newest PureBasic version
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

Re: Another graph drawing using vector library

Post by Lunasole »

Sicro wrote:With this code it also works under Linux :)
Thanks for info :) I've updated example with your fix
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
Post Reply