Drawing a line graph depending on the value of a variable

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By CatRass

I’m making a mock stock/share trading game, and I’m thinking to implement a little window that draws a line graph displaying values of a variable that is changing price. How would I use Line2D, or any other part of the Godot engine to achieve this?

:bust_in_silhouette: Reply From: whiteshampoo

You can use somthing like this:

extends Line2D

export var graph_width : float = 100
export var graph_height : float = 100

func create_graph(values : Array) -> void:
	var length : int = len(values)
	assert(length > 0 and (values[0] is int or values[0] is float))
	
	var points_ : PoolVector2Array 
	
	var maximum : float = values[0]
	var minimum : float = values[0]
	
	
	for value in values:
		assert(value is int or value is float)
		if value > maximum:
			maximum = value
		if value < minimum:
			minimum = value
			
	for index in range(length):
		points_.append(Vector2(
					lerp(0.0, graph_width, index / float(length - 1)),
					lerp(graph_height, 0.0, (values[index] - minimum) / (maximum - minimum))))
	
	points = points_
		

func _ready():
	create_graph([1, 3, 7, 6, 6, 8, 9, 7, 11, 13])

This will create a graph from Line2D in a given space (graph_height/width)
If you want ths specify minimum and maximum by yourself, you need to expand this a little bit.

Sorry, a question. Would I attach this in a script to a Line2D Node, or the Scene node. I put this in a script linked to a Line2D Node and nothing showed.

CatRass | 2020-05-26 08:58

You can easily see this in the first line of the script. if it extends Line2D, then you have to attach this to a Line2D.

It should show something out of the box, if you don’t change anything. In the ready-method it sets up an example.

Make sure, it is in in your current viewport/in view of your camera.
It does NOT show anything in the editor!

whiteshampoo | 2020-05-26 09:42

Ok thank you, I’m not sure what went wrong last time. Is it possible that it updates the graph every time a new value is generated? Since the prices constantly change I want it to change the graph accordingly

CatRass | 2020-05-26 10:11

It generates the graph from an array. It does not memorize it. you have to add your new prize to an array, or rewrite the code, so it remebers old prizes and just adds the new prize (maybe the better solution)

whiteshampoo | 2020-05-26 10:28

I see. How would I do this? Since the numbers are randomly generated there isn’t a formula I can put into the Line2D so it can generate the same numbers

CatRass | 2020-05-26 10:30

extends Line2D

export var graph_width : float = 100
export var graph_height : float = 100
export var graph_max_points : int = 0 # Zero for unlimited graph-points

var graph : Array

func create_graph() -> void:
	var length : int = len(graph)
	assert(length > 0 and graph[0] is float)
	
	var points_ : PoolVector2Array 
	
	var maximum : float = graph[0]
	var minimum : float = graph[0]
	
	if length == 1:
		graph.append(graph[0])
		length += 1
	
	for value in graph:
		assert(value is float)
		if value > maximum:
			maximum = value
		if value < minimum:
			minimum = value
			
	for index in range(length):
		points_.append(Vector2(
					lerp(0.0, graph_width, index / float(length - 1)),
					lerp(graph_height, 0.0, (graph[index] - minimum) / (maximum - minimum))))
	
	points = points_
	
func pop_values() -> void:
	if graph_max_points != 0:
		if len(graph) > graph_max_points:
			graph.pop_front()
			pop_values()

func add_value(value : float) -> void:
	graph.append(value)
	pop_values()

func _ready():
	add_value(1.0)
	add_value(2.0)
	add_value(4.0)
	add_value(3.0)
	add_value(6.0)
	create_graph()

Please try to implement such stuff by yourself.
It’s not that hard, and you’ll learn alot :wink:
The best way to get better faster is trial and error.

EDIT:
Most important thing for a beginner is to read and understand the code, if you want to copy stuff from help-boards! If you want to use it and don’t understand how it works, please ask. That saves you alot of time and more questions later on.

whiteshampoo | 2020-05-26 10:50

Thank you for being so helpful. I played around with it, and it seems to be working. I’m mostly asking questions since I’ve tried a few tutorials but I don’t think I learn much from tutorials, so I thought learning through questions should work. Any tips on how to learn and master Godot?
Also if you’re curious, my code is as follows:

extends Line2D

export var graph_width : float = 100
export var graph_height : float = 100
export var graph_max_points : int = 0 # Zero for unlimited graph-points

var graph : Array

func create_graph() -> void:
	var length : int = len(graph)
	assert(length > 0 and graph[0] is float)

	var points_ : PoolVector2Array 

	var maximum : float = graph[0]
	var minimum : float = graph[0]

	if length == 1:
		graph.append(graph[0])
		length += 1

	for value in graph:
		assert(value is float)
		if value > maximum:
			maximum = value
		if value < minimum:
			minimum = value

	for index in range(length):
		points_.append(Vector2(
					lerp(0.0, graph_width, index / float(length - 1)),
					lerp(graph_height, 0.0, (graph[index] - minimum) / (maximum - minimum))))

	points = points_

func pop_values() -> void:
	if graph_max_points != 0:
		if len(graph) > graph_max_points:
			graph.pop_front()
			pop_values()

func add_value(value : float) -> void:
	graph.append(value)
	pop_values()


var balance = 10

const MINIMUM_PRICE = 100 # no stock will be priced cheaper than this
const FACTOR = 2 # the highest possible price will be 2x your balance

func update_price(price : float) -> void:
	print("Price changes")

func _on_Timer_timeout():
	randomize()
	var price = generate_stock_price()
	update_price(price)
	add_value(price)
	add_value(2.0)
	add_value(4.0)
	add_value(3.0)
	add_value(6.0)
	create_graph()


func _ready():
	randomize()
	var price = generate_stock_price()
	update_price(price)

func generate_stock_price():
	var price = rand_range(MINIMUM_PRICE, balance * FACTOR)
# warning-ignore:shadowed_variable
	return price

Did I do it right?

CatRass | 2020-05-26 11:02

You probably shouldn’t do your stock-math in the Line2D. Thats just for drawing. Remove the “_ready” method from my example, and close the Line2D-script, until you face problems. Add your Graph-Node (Line2D) as a child (or wherever you need it to be). In you “main-game-script” (wahtever) you should reference the Line2D:

onready var Graph : Line2D = $Path_to_Line2D # Change this!

Then you can call the methods via Graph:

func _on_Timer_timeout():
    randomize()
    var price = generate_stock_price()
    update_price(price)
    Graph.add_value(price)
    Graph.create_graph()

(untested!)

Every Node/Script should only do what you expect it to do. A line-graph from your stock-prices should not generate prices! it should only display it and nothing else.

BTW:
You can also simplify one method:

func generate_stock_price():
    return rand_range(MINIMUM_PRICE, balance * FACTOR)

whiteshampoo | 2020-05-26 11:16

Thank you so much for the help, appreciate it, and thank you for the explanation :>

CatRass | 2020-05-26 11:20

You’re welcome

whiteshampoo | 2020-05-26 11:23

Hi, I’m back and I tried implementing the code you set and it’s returning a few errors. Is there a way I could put the stock math in a separate scene, and reference it in other scripts?

CatRass | 2020-05-27 00:46

Can you share you project folder? Its hard to help without your Node-Tree and Code :x

whiteshampoo | 2020-05-27 07:42

Sure, no problem, here’s the Google Drive link :>

https://drive.google.com/open?id=11JA6OQrH4uwP3BL21rDiAKF5iGS-uH8X

CatRass | 2020-05-27 07:47

workupload - Are you a human?

Please read all the “new” code carefully and try to understand everyting!
Btw, i tweaked the timer to be much faster, so i/you can see the result faster…

I put some love in it, and i would be really disappointed if it would be wasted :wink:
Btw you should probably use 1D-Noise for your pricing. Its KIND of random, but much smoother and more believable.

whiteshampoo | 2020-05-27 08:34

Hello, it’s me again! I’ve done some reading on 1D-Noise and I’m not entirely sure how to use this for more accurate pricing. Could you explain how I’d be able to use it?

CatRass | 2020-05-29 09:38

Game_UI.gd:

extends Node2D

const MINIMUM_PRICE = 100.0 # no stock will be priced cheaper than this
const FACTOR = 2.0 # the highest possible price will be 2x your balance
const PROGRESS = 1.0 # adds to noise_progress

var balance : float = 10.0
var price : float = MINIMUM_PRICE
var noise : OpenSimplexNoise
var noise_progress : float = 0.0

onready var pricelabel : Label = $PriceLabel 
onready var graph : Line2D = $Graph

func _ready():
	randomize()
	
	noise = OpenSimplexNoise.new()
	noise.seed = randi()
	
	# --- tweak this to your needs ---
	noise.octaves = 4
	noise.period = 20.0
	noise.persistence = 0.8
	# --------------------------------
	
	generate_stock_price()


func generate_stock_price():
    var noise_modify : float = (balance * (FACTOR / 2.0))
	price = MINIMUM_PRICE + noise_modify 
	price += noise.get_noise_1d(noise_progress) * noise_modify 
	noise_progress += PROGRESS
	pricelabel.update_price(price)
	graph.add_value(price)
	graph.create_graph()

func _on_Timer_timeout():
	generate_stock_price()

func _on_Button_pressed():
	get_tree().change_scene("res://Title Screen/title_screen.tscn")

Graph.gd

(i made some mistakes, here is a better (working) version)

extends Line2D

export var graph_width : float = 100
export var graph_height : float = 100
export var graph_max_points : int = 0 # Zero for unlimited graph-points
export var grow : bool = false

var graph : Array

func create_graph() -> void:
	var length : int = len(graph)
	assert(length > 0 and graph[0] is float and graph_max_points >= 0)

	var points_ : PoolVector2Array = []

	var maximum : float = graph[0]
	var minimum : float = graph[0]

	if length == 1:
		graph.append(graph[0])
		length += 1

	for value in graph:
		assert(value is float)
		if value > maximum:
			maximum = value
		if value < minimum:
			minimum = value
			
	if minimum == maximum:
		minimum -= 1.0
		maximum += 1.0

	for index in range(length):
		points_.append(Vector2(
			0.0,
			lerp(graph_height, 0.0, (graph[index] - minimum) / (maximum - minimum))))
		if grow and graph_max_points:
			points_[-1].x = lerp(0.0, graph_width, index / float(graph_max_points - 1))
		else:
			points_[-1].x = lerp(0.0, graph_width, index / float(length - 1))

	points = points_

func pop_values() -> void:
	if graph_max_points != 0:
		if len(graph) > graph_max_points:
			graph.pop_front()
			pop_values()

func add_value(value : float) -> void:
	graph.append(value)
	pop_values()

Just copy&paste it. It should be pretty easy to understand what i changed, and how it works.

If you want to understand the OpenSimplexNoise, i would recommand you, to create a noise-texture, and play with the values in the inspector. There you see a live-preview of what you would get. Just imagine a line from left to right in the texture. Thats what the 1D-Noise whould be. black is -1.0, grey is 0.0 and white is 1.0. Darker grey is then somthing between -1.0 and 0.0.

whiteshampoo | 2020-05-29 10:17

Hello, it’s me again! I implemented a saving and loading mechanic, and want to make sure the stock price would be saved and loaded too. It saves, and it’s in the save file, but it doesn’t get loaded. Would the problem be with the variable itself or the way price is generated?

CatRass | 2020-06-01 23:20

Please start a new topic :slight_smile:
dont forget to show us your code and make a good description what you expect to happen, and what does not work.

whiteshampoo | 2020-06-02 11:23

Yes, sorry of course, just you’ve been so helpful I grew accustomed to going to you for help. Thank you :>

CatRass | 2020-06-02 11:36

Its just because the title is something about graphs, and now you have trouble with saving/loading :wink:
Maybe in the future someone has similar problems like you, and than it’s easy to find, if the problem matches the title :stuck_out_tongue:

whiteshampoo | 2020-06-02 14:54