Another graph drawing using vector library

Share your advanced PureBasic knowledge/code with the community.
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 »

Sorry for the offtopic, but:
How can the first post have a later date than the second post? Mysterious.

Thank you for updating. I will add in shorttime the code to the CodeArchiv.
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 »

Updated 1st post to a newer version (where simple mouse interactions added).

I'd like to make completely standalone thing from that all... but well that still doesn't looks "justified" as it's not used so often ^^
So nothing to do, code needs to be slightly customized every time when used

// also hope that bug with canvas SetGadgetAttribute() on Linux fixed yet, if not -- see @Sicro fix
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
AZJIO
Addict
Addict
Posts: 2143
Joined: Sun May 14, 2017 1:48 am

Re: Another graph drawing using vector library

Post by AZJIO »

I am trying to make multiple graphs on the same field. You can set the resolution of the graph, that is, the number of points per wave (Step 10). A straight line can be specified with two points.

Image

Code: Select all

EnableExplicit


Global.d cX0, cY0
Global.d mX, mY
Global xMax2, yMax2

;{ Simple Graph }

; Еще одна штука для простых "визуальных эффектов"
;   2017         (c) Luna Sole
;    v1.0.0.4       (+ взаимодействие с мышью)         

; Рисует график с двумя линиями на заданном изображении с использованием векторной библиотеки PB
; hImgOut            Изображение PB для рисования может быть любого размера [кроме, возможно, «очень низкого» разрешения ^^]
; xMax, xMin                  максимальная и максимальная величина X графика
; yMax                  максимальная величина Y графика
; GridStepX            Шаг сетки X [вертикальные линии], измеренное в количестве GraphData (). используйте 0 для отключения
; GridStepY            Шаг сетки Y [горизонтальные линии], измеренное в значениях GraphData (). 0 для отключения
; FontSize            размер шрифта текстовых меток. используйте 0, чтобы отключить метки
; ColorGrid            цвета для элементов графика (RGB)
; RETURN:            нет, изображение изменено в случае успеха
Procedure DrawGrid(hImgOut, xMax, xMin, yMax, GridStepX = 10, GridStepY = 10, FontSize = 9, ColorGrid = $BBFFFFFF)
   Protected maxValues ; разница между xMax - xMin
   Protected t ; переменная цикла для перечисления во времени, по оси Y
   If yMax < 1
      ProcedureReturn   ; выйти, если данные из одной точки, график должен быть из нескольких точек по оси Y
   EndIf
   yMax2 = yMax   ; задаём внешний, чтобы перечислять в цикле
   
   ; сделать вещи, чтобы лучше подогнать фоновую сетку
   If GridStepY > 0
      If xMax >= 0
         xMax + GridStepY - xMax % GridStepY
      ElseIf xMax
         xMax - xMax % GridStepY
      EndIf
      If xMin > 0
         xMin  - xMin  % GridStepY
      ElseIf xMin < 0
         xMin  - (GridStepY + xMin  % GridStepY)
      EndIf
   EndIf
   maxValues = xMax - xMin
   If maxValues <= 0
      maxValues = 1
   EndIf
   
   ; минимум значений оси Х
   xMax2 = xMax
   
   ; загрузить шрифт для текстовых меток
   Protected Font
   If FontSize > 0
      Font = LoadFont(#PB_Any, "arial", FontSize)
      If Not IsFont(Font) ; и так далее
         Font = LoadFont(#PB_Any, "tahoma", FontSize)
         If Not IsFont(Font) ; и так далее.. :)
            Font = LoadFont(#PB_Any, "consolas", FontSize)
         EndIf
      EndIf
   EndIf
   
   ; рисовать данные в изображение
   If StartVectorDrawing(ImageVectorOutput(hImgOut, #PB_Unit_Pixel))
      Protected.d oX = 1.0, oY = oX
      Protected.d mtW = 1.0, mtH = mtW
      ;   временные переменные, используемые при рисовании
      Protected.d cX, cY
      Protected.d tLast
      ; [n] - определить смещения графиков / размеры текстовых меток и т. д.
      If IsFont(Font)
         VectorFont(FontID(Font), FontSize)
         If VectorTextWidth(Str(xMax)) > VectorTextWidth(Str(xMin)) ; задаём ширину текста
            oX = VectorTextWidth(Str(xMax)) + 2.0
         Else
            oX = VectorTextWidth(Str(xMin)) + 2.0
         EndIf
         oY = VectorTextHeight("0A") + 2.0
         If oX > oY
            oY = oX
         Else
            oX = oY
         EndIf
         mtW = VectorTextWidth(Str(yMax)) * 1.2
         mtH = VectorTextHeight(Str(xMax)) * 0.8
      EndIf
      ;    множители для масштабирования координат графика
      mX = (VectorOutputWidth() - oX * 2.0) / (yMax + Bool(yMax = 0))   
      mY = (VectorOutputHeight() - oY * 2.0) / maxValues
      
      ; [0] - рисовать текстовые метки и линии сетки
      VectorSourceColor(ColorGrid) ; цвет меток и сетки шкалы
                            ;   горизонтальные линии / метки
      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
               ; нарисовать текстовую метку
               If IsFont(Font) And cY < tLast
                  MovePathCursor(oX - (2.0 + VectorTextWidth(Str(xMax - t))), cY - VectorTextHeight(Str(xMax - t)) / 2)
                  DrawVectorText(Str(xMax - t))
                  tLast = cY - mtH
               EndIf
               ; нарисовать линию сетки
               MovePathCursor(oX, oY + mY * t)           
               AddPathLine(oX + yMax * mX, oY + mY * t)
            EndIf
         Next
      EndIf
      ;   вертикальные линии / метки
      tLast = 0.0
      If GridStepX > 0   
         ;GridStepX + 1
         For t = 0 To yMax
            If t % GridStepX = 0 Or t = yMax
               cX = oX + t * mX
               cY = oY + maxValues * mY
               ; нарисовать текстовую метку
               If IsFont(Font) And cX > tLast
                  MovePathCursor(cX - VectorTextWidth(Str(t)) / 2, cY + 2.0)
                  DrawVectorText(Str(t))
                  tLast = cX + mtW
               EndIf
               ; нарисовать линию сетки
               MovePathCursor(cX, oY)
               AddPathLine(cX, cY)
            EndIf
         Next
      EndIf
      ;   fin
      If GridStepX > 0 Or GridStepY > 0
         DashPath(1.0, 3.0)
      EndIf
      cX0 = oX
      cY0 = oY
      
      ;    StrokePath(LineWidth)
      
      ; очистить
      StopVectorDrawing()
   EndIf
   
   ; очистить
   If IsFont(Font)
      FreeFont(Font)
   EndIf
EndProcedure

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Тест / пример
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; используется для обработки кликов мыши по графику
; mapkey - координата X изображения графика, value - соответствующий индекс в массиве GraphData()
Global NewMap GraphGeodata()

; Размеры графика
Global W = 800, H = 600
; Изображение для рисования графика
Global tImg
; Окно и холст для отображения графика
Global tWindow, tCanvas

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

; сгенерировать графические данные
; 127 определяет число точек по оси Y

; создать изображение для получения вывода
tImg = CreateImage(#PB_Any, W, H, 32, $1B1B1B) ; цвет фона тут
DrawGrid(tImg, 60, -60, 127, 10, 10, 12)
; нарисовать график на созданном изображении
; Debug mX ; = 5.9
; Debug mY ; = 4
; Debug cX0 ; = 19.34375
; Debug cY0 ; = 19.34375


Define.d tmp, LineWidth = 1
Define t, j
Global Plot_Dot.b = 0
; Color            цвет линии графика (RGB)
; LineWidth     ширина линии (в пикселях)
; Plot_Dot      показывать ли точки на графике

Procedure DrawGraph(tmp.d, t)
   ;   временные переменные, используемые при рисовании
   Protected.d cX, cY
   cX = cX0 + mX * t
   cY = cY0 + mY * (xMax2 - tmp)
   If t = 0
      MovePathCursor(cX0, cY)
   EndIf
   AddPathLine(cX, cY)         ; добавить линию
   If Plot_Dot
      AddPathCircle(cX, cY, 2.0)   ; добавить точку
   EndIf
   MovePathCursor(cX, cY)      ; восстановить позицию курсора
EndProcedure


If StartVectorDrawing(ImageVectorOutput(tImg, #PB_Unit_Pixel)) ; открываем рисование
   
   ; рисуем синусоиду (выпрямленный ток) жёлтая
   VectorSourceColor($FF00B1CB) ; задаём цвет линии (с добавлением альфа-канала слева ARGB)
   j = 45
   For t = 0 To yMax2
      tmp = 60 * Abs(Sin(Radian(j-90)))
      j+5
      DrawGraph(tmp, t)
   Next
   StrokePath(LineWidth) ; рисует график в заданном цвете
   
   
   ; рисуем поверх косинусоиду
   VectorSourceColor($FF80A800)
   j = 0
   For t = 0 To yMax2
      tmp = 60 * Cos(Radian(j-90))
      j+5
      DrawGraph(tmp, t)
   Next
   StrokePath(LineWidth)
   
   ; рисуем поверх синусоиду
   VectorSourceColor($FF9900FF) ; $00A800
   j=0
   For t = 0 To yMax2
      tmp = - 60 * Sin(Radian(j-90))
      j+5
      DrawGraph(tmp, t)
   Next
   StrokePath(LineWidth)
   
;    Plot_Dot = 1
   ; рисуем экпоненту
   VectorSourceColor($FF2A04FF)
   For t = 0 To yMax2 ; Step 6
      ;    tmp = t * 0.8 - 60 ; линия наклонная
      tmp = 60 * Exp(- t / 24) ; экпонента
      DrawGraph(tmp, t)
   Next
   StrokePath(LineWidth)

   ; рисуем линию
   VectorSourceColor($FF00A800)
   DrawGraph(- 60, 0) ; начало
   DrawGraph(127 * 0.8 - 60, 127) ; и конец линии
;    For t = 0 To yMax2 Step 127
;       tmp = t * 0.8 - 60 ; линия наклонная
;       DrawGraph(tmp, t)
;    Next
   StrokePath(LineWidth)
   
   ; рисуем треугольную
   VectorSourceColor($FFFF8C00)
   For t = 0 To yMax2
      tmp = 20 * (t/10 - 2 * Round((t/10 + 1) / 2, #PB_Round_Down)) * Pow(-1, Round((t/10 + 1) / 2, #PB_Round_Down)) - 40 ; Треугольный
      DrawGraph(tmp, t)
   Next
   StrokePath(LineWidth)
   
   
   StopVectorDrawing()
EndIf


; показать окно с графиком
tWindow = OpenWindow(#PB_Any, 0, 0, W, H, "График", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

; использовал ImageGadget вместо CanvasGadget, так заработало на Linux
CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
		tCanvas = CanvasGadget(#PB_Any, 0, 0, W, H)
		SetGadgetAttribute(tCanvas, #PB_Canvas_Image, ImageID(tImg))
    CompilerCase #PB_OS_Linux
		ImageGadget(#PB_Any, 0, 0, W, H, ImageID(tImg))
CompilerEndSelect

; освободить изображение
FreeImage(tImg)

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
Added to work in Linux
Last edited by AZJIO on Mon Jan 18, 2021 1:32 pm, edited 1 time in total.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Another graph drawing using vector library

Post by Kwai chang caine »

Very nice
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
User avatar
digital32
User
User
Posts: 30
Joined: Fri Dec 28, 2012 1:28 am

Re: Another graph drawing using vector library

Post by digital32 »

Updated Code with some more options.

Code: Select all

;{ Simple Graph }

; Yet another stuff for simple 'visuals'
;	2017			(c) Luna Sole
; 	v1.0.0.4 		(+ mouse interaction)
  ; Digital32 Updates:
  ; Original Code - Forum Post: https://www.purebasic.fr/english/viewtopic.php?t=67464&hilit=graph
  ;   10/18/2023 - Compile and Execute Test with PB 6.03
  ;   10/18/2023 - IsFont was not working - Added Check for Font = 0
  ;   10/18/2023 - Added "TextColor" option to control grid and text color
  ;   10/18/2023 - Changed DrawSimpleGraph() Procedure input: Color1/Color2 to DataPointColor/AvgColor - More Descriptive
  ;   10/22/2023 - Added Warning and Failure Line Color Changing Option (For Thresholds in the Graph)
  ;   01/04/2024 - Compile and Execute Test with PB 6.04
  ;   10/08/2024 - Compile and Execute Test with PB 6.12


; 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
  ; DataPointColor          color of data points
  ; AvgColor                color of average line
  ; TextColor               color of Grid Lines and Text
  ; DataPointWarningValue   data point >= warning value. use 0 to disable
  ; DataPointWarningColor   color of line and circle if >= DataPointWarningValue
  ; DataPointFailValue      data point >= failure value. use 0 to disable
  ; DataPointFailColor      color of line and circle if >= DataPointFailValue
	; RETURN:				 none, image modified on success
	Procedure DrawSimpleGraph(hImgOut, Array GraphData(1), Map OutGeoData(), Average, GridStepX = 10, GridStepY = 10, FontSize = 9, DataPointColor = $00DDDD, AvgColor = $DDDD00, TextColor = $000000, DataPointWarningValue = 0, DataPointWarningColor = $00FFFF,DataPointFailValue = 0, DataPointFailColor = $0000FF)
		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) And Font <> 0
			  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(TextColor | $FF000000)
			Protected.d tLast
			;	horizontal lines/labels
			tLast = oY + maxValues * mY + 5.0
			If GridStepY > 0
			  For t = maxValues To 0 Step -1
			    ;Debug("maxValues: " + maxValues)
					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( DataPointColor | $FF000000) 
			MovePathCursor(oX, oY + mY * (highValue - GraphData(0)))
			For t = 0 To maxItems
				cX = oX + mX * t
				cY = oY + mY * (highValue - GraphData(t))
        ; See If Warning Or Failure are > 0 to Color the Line and Circle
				If GraphData(t) >= DataPointFailValue And DataPointFailValue > 0
				  VectorSourceColor(DataPointFailColor | $FF000000)
				ElseIf GraphData(t) >= DataPointWarningValue And  DataPointWarningValue > 0
				  VectorSourceColor(DataPointWarningColor | $FF000000)
				EndIf		
				AddPathLine(cX, cY)			; add line
				AddPathCircle(cX, cY, 2.0)	; add spot at the edge
				If (GraphData(t) >= DataPointWarningValue And GraphData(t) < DataPointWarningValue) And (DataPointWarningValue > 0) ; Fill Circle if Warning 
				  FillPath(#PB_Path_Preserve)
				EndIf
				If (GraphData(t) >= DataPointFailValue) And (DataPointFailValue > 0) ; Fill Circle if Failure
				  FillPath(#PB_Path_Preserve)
				EndIf
				StrokePath(LineWidth)
        VectorSourceColor(DataPointColor | $FF000000) ; Reset to Default				
				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(AvgColor | $FF000000)
				StrokePath(LineWidth)
			EndIf
			
			; cls
			FreeList(Avg())
			StopVectorDrawing()
		EndIf
		
		; cls
		If IsFont(Font)
			FreeFont(Font)
		EndIf
	EndProcedure
	
;}
User avatar
minimy
Enthusiast
Enthusiast
Posts: 552
Joined: Mon Jul 08, 2013 8:43 pm
Location: off world

Re: Another graph drawing using vector library

Post by minimy »

Thanks for share Lunasole! very nice graphs! work nice in win10 and 11 (32 and 64bit) PB6.12LTS

But i have this message in line 274:
'ERROR line 274 Number in sigle quotes ('') is limited to 1 character.'
After change this RandomSeed('LS') with this RandomSeed('S'), work.

Keya you need buy a new computer with modern windows! :lol:
Sorry, just kidding :mrgreen:
Keya wrote: Fri Jan 13, 2017 11:56 pm 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!
If translation=Error: reply="Sorry, Im Spanish": Endif
Post Reply