MISSION CRITICAL: problem with newLine function

Hi All, 

We have 120 middle schoolers who were unable to use our app today because we are struggling with real-time graphing and the display.newLine function.

We were scheduled to implement a robot graphing activity in class today but had to postpone because we can’t get the newLine function to plot values properly.

 

Essentially we place a newLine object on the scene and append coordinates to it as the data streams from the robots.

 

Here is the relevant code:

 

priGraph = display.newLine(graphGroup, xposition, yposition, xposition, yposition)

 

function handleStream (data)

– data is a string “<1 or 2 which bot to graph> <x value> <y value>”

–EXAMPLE: “1 4.77399999997579 0.497735107421931” 

– we have a function that splits this into a table

local table = {“1”,  “4.77399999997579”, “0.497735107421931”}

priGraph:append(tonumber(table[2]), tonumber(table[3]))

end

 

We have been unable to get this to plot successfully with the tonumber() function.  We print out the string value and the resulting number value and they match as expected.  But when we append them to the Line object we get different results.

 

We need to take the above string and plot a new point on the graph to extend the line, basically real time graphing.  We are doing this at a rate of about 10 times per second.  Any help you can give would be much appreciated.  We really need to use this app with 120 students tomorrow.

 

Thanks,

–Scot

 

Scot M. Sutherland

Ph.D. Candidate, Learning and Mind Sciences

UC Davis School of Education

xposition, yposition, xposition, yposition

begining and end of line in the same point - obviouslywrong

What do you mean by “it doesn’t work as expected?”, exactly?

local myLine = display.newLine( group, x1, y1, x2, y2) – Create the initial line

Appending should be fine after that, if small and white (default values being 1px and white for newLine)

Sorry to disappoint you.  The first coordinates are required by newLine, but there is no data to graph.  So the two points are intentionally the same.  When data streams from the server it is appended to the line forming a graph.  If there is a better way to declare a newLine object to fill with a line in real time, we’d be happy to know about it.

We have Cartesian graphing area on which we plot the data arriving from the server.  We simply want to plot the data on the graph.  Each unit is 25 pixels apart.  We are able to plot the graph with hardcoded numbers and with math on numbers.  When we try to use the tonumber() function we get a crazy result that makes little sense.  We are plotting at a rate of about 10 times per second.  Is the speed of the plots part of the problem?

Well, it certainly would help speed if you localize tonumber() first but the speed shouldn’t be a problem. I did a quick test project every 100ms, repeated 300 times, and it worked just fine on my Macbook.

What I would do next is instead of using tonumber() within append, do it seperately. Assign to a variable. then print both the number and format.

local number1 = tonumber(myValue)

print(number1, type(number1)) – ensure it’s definitely numbers you’re working with! Maybe it’s something where string.gsub or whatever you’re using is trying to tonumber() something it can’t actually swallow.

The other question (since I don’t see anything else from your code) is does the line start out fine but go bad? What happens if you force the line to have a minimum distance first? (like, -5, 0, 0, 0)

(It’s hard to debug with so little code to work from…)

you can ignore the fact that your initial 1-point “line” is degenerate - Corona doesn’t care.

and let’s take you at your word that you’re correctly parsing the text to numbers.

here’s the clue’s that i’m picking up on:

  • your coordinates seem to be 0-based, so translation is implied (to keep them all on screen)

  • you want a graph where your units are 25 pixels wide, so scaling is also implied

  • you’ve inserted your line into “graphGroup”

  • you’ve described the result as “crazy” :wink:

is the translation/scaling occuring on the group?

any chance this is old “graphics v1” era code, and you’ve since updated Corona?

have you tried simply adding graphicsCompatibility=1 to config.lua?

Going to be as clear as I can.  I have about 5 hours to get this working or our research opportunity will be severely hampered.

Here is the Line display object:

local primaryGraph = display.newLine(Lib.graph.group, Data.grid.originX, Data.grid.originY, Data.grid.originX, Data.grid.originY) primaryGraph.stroke = gui.magenta primaryGraph.strokeWidth = 20 Data.priGraph = primaryGraph

Here is the code that attempts to update the line:

print("DATA DATA:\>\> ", data) local dtable = string.split(data, "%S+") table.dump(dtable) print("dtable[2] as number", tonumber(dtable[2])) print("dtable[3] as number", tonumber(dtable[3])) local value = tonumber(dtable[3]) \* 25 Data.priGraph:append(Data.grid.originX, Data.grid.originY - value) Lib.robot.position(Data.primaryBot, Lib.affected.graph, Lib.affected.xAxis, Lib.affected.yAxis, tonumber(dtable[2]), tonumber(dtable[3]))

The last line shows that the tonumber() function is working just fine for my other purposes (in this case it is used to check if the number is positive or negative while positioning the robot on the screen. Like tonumber(dtable[2] > 0.

Ouput:

DATA DATA:>> 1 6.98899999999958 1.96590917968751

table.dump…

[1]=1,

[2]=6.98899999999958,

[3]=1.96590917968751,

dtable[2] as number 6.9889999999996

dtable[3] as number 1.9659091796875

Experimenting:

If I declare value outside the scope of the function I’m using and do the following I get the line I expect–increasing along the y axis.

local primaryGraph = display.newLine(Lib.graph.group, Data.grid.originX, Data.grid.originY, Data.grid.originX + 1, Data.grid.originY + 1) value = value + 1 Data.priGraph:append(Data.grid.originX, Data.grid.originY - value)

Notice that I had to add 1 to the second data point in the line to get this effect.

These all work:

value = value + tonumber(“1”)

local dx = tonumber(“0.9854533691406”)

value = value + dx


local dx = tonumber(“0.9854533691406”) * 25

value = value + dx


value = value + tonumber(“0.9854533691406”) * 25

These do not work:

value = tonumber(dtable[3])


local dx = tonumber(dtable[3])

value = value + dx


value = tonumber(“0.9854533691406”) * 100  – Gives a weird triangular shape not a line.


value = tonumber(dtable[3]) * 100    – Gives nothing


local dx = tonumber(dtable[3]) * 100  – Nothing

value = dx


local dx = tonumber(dtable[3]) * 100              – Nothing but local dx = tonumber(“1”) * 100 works

value = value + dx


The code you’ve provided is not very helpful, I’m afraid. You’ve used a couple of non-corona methods (string.split, table.dump, robot.position…), didn’t show us the Runtime structure, didn’t show us how the data string is assembled…it’s just impossible for one of us to test or reconstruct the code you are using. As such, with no obvious typo/format issues there’s no way for us to tell where the problem would be.

(The only other thing I can say is that using big decimal numbers for pixel display is not winning you anything, either. You should be using math.round/ceil/floor to set pixel position unless you have a very good reason not to. But that shouldn’t be breaking your code.)

As best I can tell, this is what you’re doing, and this works fine on sim. If you’re getting strange shapes then it sounds like either string.split or whatever is making your data string is passing off bad / wrong / strange positions. Maybe it’s using group position instead of global position or vice versa. That sort of thing. Hopefully you are not using .anchorChildren either.

 local originX, originY = 100, display.contentHeight - 5 local graph = display.newGroup() -- Create the line object local primary = display.newLine(graph, originX, originY, originX+1, originY) primary.stroke = {1,0,1} -- magenta primary.strokeWidth = 25 local function handleStream() originX = originX + 1.4587588787276 originY = originY - 1.6779827711721 primary:append( originX, originY ) end timer.performWithDelay(100, handleStream, 300)

why don’t you do “something” like below, just to “prove” to yourself if (or not) your string parsing is working, to narrow down the problem, fe:

-- you should use ACTUAL captured data here -- i just made some up local sampleData = { "1 0.6358531449324 -3.0669576097903", "2 0.8500930814539 -0.2012695699942", "1 3.9596240119633 3.2284005249184", "2 -3.2589190343944 3.5894344920194", "2 0.13534958952605 -1.9600512710959", "1 -4.0859706411939 -1.3554795983764", "1 -3.3410138248848 4.8852504043703", "1 -3.8091677602466 -4.9533066805017", "1 -1.221198156682 0.31662953581347", "2 1.0176396984771 1.0716574602496", "1 1.6304513687551 -0.49211096530045", "1 -4.4296090578936 1.0768456068606", "2 3.0260628070925 0.19882808923612", "1 3.7597277748955 2.2667622913297", "2 4.2571794793542 0.39353617969298", "1 -0.37919248023927 -2.6467177343059", "2 -2.9039887691885 2.796563615833", "2 4.9679555650502 4.9969481490524", "2 -1.0756248664815 -2.3378704184088", "1 3.4014404736473 -4.7625659962767", } for i=1,#sampleData do -- i don't have your .split() fn so instead: local botn,botx,boty = string.match(sampleData[i], "(%S\*)%s(%S\*)%s(%S\*)") -- verify got all parts as strings print(i, botn,botx,boty, type(botn),type(botx),type(boty)) -- convert to numbers botn,botx,boty = tonumber(botn),tonumber(botx),tonumber(boty) -- verify got all parts as numbers print(i, botn,botx,boty, type(botn),type(botx),type(boty)) -- double-check by converting back to string, back to number -- NB: this isn't guaranteed to work for EVERY double, -- as error can creep into the conversion, -- but likely ok for your values (and the sample values above): local chkn,chkx,chky = tonumber(tostring(botn)),tonumber(tostring(botx)),tonumber(tostring(boty)) -- then compare: print(i, tostring(botn==chkn), tostring(botx==chkx), tostring(boty==chky)) end

then you can proceed from there, ie: either your parsing is ‘bad’ so focus there, or it’s ‘good’ so focus on the line (where i’d still try v1 compatibilitly as a first step)

fwiw, hth

mystring.split = function (str, pattern, out) out = out or { } for word in string.gmatch(str, pattern) do table.insert(out, word) end return out end

changed the name to make sure I’m not colliding with anything in the string object.

mytable.dump = function (table) local out = "mytable.dump..." for key, value in pairs(table) do out = out.."\n\t["..key.."]="..value.."," end print(out) return out end

This the position function that puts the robot in the correct place.  It also scrolls the screen.  The position function works perfectly using the same data that I’m trying to use for the graph.  It is more complicated because it scrolls the graph behind the robot when it reaches the edge of the screen.

This function has no problems:

 position = function(bot, graph, xAxis, yAxis, xpos, ypos) local function yScroller (yAxis, graph, origin, ypos) if ypos \< 8 and ypos \> -9 then y = ypos elseif ypos \> 7.9999 and ypos \< 18 then y = 8 elseif ypos \< -8.9999 and ypos \> -18 then y = -9 elseif ypos \> 17.9999 and ypos \<= 20 then y = ypos - 10 elseif ypos \< -17.9999 and ypos \>= -20 then y = ypos + 9 elseif ypos \>= 20 then y = 10 elseif ypos \<= -20 then y = -10 end if ypos \< 8 and ypos \> -9 then yAxis.y = yAxis.origin.y graph.y = 0 else yAxis.y = 25 \* (ypos - y) + yAxis.origin.y graph.y = 25 \* (ypos - y) end return y \* -25 + origin end bot.group.y = yScroller (yAxis, graph, bot.group.originY, ypos) graph.x = -25 \* xpos xAxis.x = -25 \* xpos + yAxis.origin.x end

Hi davebolliger,

Forgot to mention that setting v1 compatibility screwed everything up.  I picked up Corona SDK in January 2014.  Don’t think I have old versions anywhere.

I have a kluge working.  It does put a graph down that will work for the students, but it isn’t the clean looking graph I expected.  Need to know how to erase the points and redraw the graph for the next run.  Don’t see a method for this in the Line object.  Looks like I’ll have to kill the first one and drop another, unless there is a better way.

Still interested in a clean solution to this problem.  I did use data like the above for a test, took it straight from the data server output.  The results are the same.  Odd the the position function moves the robot perfectly but, the line function wonks out.

Here is the kluge:

Line object:

primaryGraph = display.newLine(Lib.graph.group, Data.grid.originX, Data.grid.originY, Data.grid.originX + 1, Data.grid.originY + 1) primaryGraph.stroke = botColor[Data.color.index] primaryGraph.strokeWidth = 10

Function that updates it:

local valueX = 0 local valueY = 0 local stable = {} onDataReceive = function (data) print("DATA DATA:\>\> ", data) stable = mystring.split(data, "%S+") -- mytable.dump(dtable) if stable[1] == "1" then print ("tonumber(stable[3])",tonumber(stable[3]), type(tonumber(stable[3]))) stable[3] = tonumber(stable[3]) local dy = stable[3] \* 25 local dx = tonumber(stable[2]) \* 25 print(type(dx)) valueY = valueY \* .01 + dy valueX = dx Data.priGraph:append(Data.grid.originX + valueX, Data.grid.originY - valueY) Lib.robot.position(Data.primaryBot, Lib.affected.graph, Lib.affected.xAxis, Lib.affected.yAxis, tonumber(stable[2]), tonumber(stable[3])) else end end

Hi Everyone,

Unfortunately we missed our chance yesterday.  However, we have 5 more days of data collection scheduled for the next two weeks.  I set up a test program that isolated the problem.  

Basically, the newLine object is extremely finicky about consecutive duplicate values or values that are very close.  These occur quite often if the robot is moving very slowly.  Identical values interrupt the line and values that are very close produce erratic results  I have attached a copy for others to review.

Thanks for all your efforts.  Perhaps Corona Labs might want to take a look at this.

local storyboard = require "storyboard" local scene = storyboard.newScene() local centerX = display.contentCenterX local centerY = display.contentCenterY local unitSize = 100 -- Data with duplicate values local data = { "1 -3.98 0.01 ", "1 -3.75 0.12 ", "1 -3.49 0.29 ", "1 -3.34 0.41 ", "1 -3.03 0.68 ", "1 -3.03 0.68 ", "1 -3.03 0.68 ", "1 -2.88 0.81 ", "1 -2.88 0.81 ", "1 -2.88 0.81 ", "1 -2.74 0.95 ", "1 -2.60 1.06 ", "1 -2.60 1.06 ", "1 -2.60 1.06 ", "1 -2.60 1.06 ", "1 -2.44 1.18 ", "1 -2.28 1.32 ", "1 -2.28 1.32 ", "1 -2.28 1.32 ", "1 -2.28 1.32 ", "1 -2.13 1.45 ", "1 -1.99 1.58 ", "1 -1.83 1.71 ", "1 -1.69 1.83 ", "1 -1.53 1.95 " } --[[-- Data cleaned to remove duplicate values local data = { "1 -3.98 0.01 ", "1 -3.75 0.12 ", "1 -3.49 0.29 ", "1 -3.34 0.41 ", "1 -3.03 0.68 ", "1 -2.88 0.81 ", "1 -2.74 0.95 ", "1 -2.60 1.06 ", "1 -2.44 1.18 ", "1 -2.28 1.32 ", "1 -2.13 1.45 ", "1 -1.99 1.58 ", "1 -1.83 1.71 ", "1 -1.69 1.83 ", "1 -1.53 1.95 " }]] local function extract (str, pattern) local out = {} for word in string.gmatch(str, pattern) do if type(tonumber(word)) ~= "number" then print("not a number") else table.insert(out, tonumber(word)) end end return out end function scene:createScene (event) local group = self.view local startLocs = extract(data[1], "%S+") local startX = centerX + startLocs[2] \* unitSize local startY = centerY + startLocs[3] \* unitSize local testGraph = display.newLine(group, startX, startY, startX + 1, startY + 1) testGraph.stroke = {1,.2,1} testGraph.strokeWidth = 10 local datum = 1 local addSegment = {} function addSegment:timer (event) print ("string of data: ", data[datum]) local dtable = extract(data[datum], "%S+") print ("dtable[2] number?: "..type(dtable[2]), "value: "..dtable[2]) print ("dtable[3] number?: "..type(dtable[3]), "value: "..dtable[3]) testGraph:append(centerX + dtable[2] \* unitSize, centerY - dtable[3] \* -unitSize) if datum == #data then timer.cancel(event.source) end datum = datum + 1 end timer.performWithDelay(100, addSegment, 0) end scene:addEventListener("createScene", scene) return scene

Submitted a bug report.