Is it possible to set vector path points with JavaScript?

I can sort of do this with svg and change svg points in a polyline, but I was wondering if it’s possible to access the points of Hype’s native polygon and line tools?

The idea is to make an interactive line graph, data for the graph will be set from an array of values which will control the vertical position of the points, horizontal position should be evenly spaced by incrementing a step value.

Thanks for any pointers.

no direct support by Hype.

simluar question and an approach:

hint:
you may want to use a js-chart-library (billboard or others ...) or may be svg.js

https://svgjs.com/docs/3.0/shape-elements/#svg-polyline

Thanks @h_classen
I just saw that post a minute after posting my question.

1 Like

additional lookup:
https://www.w3.org/TR/SVG/paths.html#PathData

So, this seems doable: In the uploaded sample file you can drag the “year” right and left to animate the lines. The red line was set manually and the points copied over onto a motion path to control the little circle.

The blue line was also created with Hype’s native Vector Shape tool. I gave the shape an id in the inspector “MyChart”. And was able to run this script to set attribute of the path “d”. (see below)

It works, however setting the points is still a manual task, so I had to cut and paste '+ rankings[1]*10 +' at every step. Idealy I would be able to do it like:

MyChart.setAttribute('d', [horizontalStep[i], rankings[i]]);

But the script below is a start…NativePath.hype.zip (21.7 KB)

$.getJSON("https://spreadsheets.google.com/feeds/list/xxxxxxxxxxxxxx/od6/public/values?alt=json", function(data) { 
 
  var rankings = [];
  
  for (i = 0; i < data.feed.entry.length; i++) { 
  
  rankings.push(data.feed.entry[i].gsx$stephanedberg.$t)

  var MyChart = hypeDocument.getElementById("myChart_path");
  
} 
    
  MyChart.setAttribute('d', 'M5.00 '+ rankings[0]*10 +' L 56.00 '+ rankings[1]*10 +' L 108.00 '+ rankings[2]*10 +' L 165.00 '+ rankings[3]*10 +' L 219.00 '+ rankings[4]*10 +' L 275.00 '+ rankings[5]*10 +' L 335.00 '+ rankings[6]*10);
  
  });
1 Like

Managed to work out the steps and array! :star_struck:
Seems pretty solid so far. Does anyone know how to take that approach and apply it to the points of the motion path so I can repeat the effect created in the red line?

$.getJSON("https://spreadsheets.google.com/feeds/list/xxxxxxxxxxx/od6/public/values?alt=json", function(data) { 
  
  var rankings = [];
  var steps = [];
  var pairsArray = [];
  var step = 0;
  
  for (i = 0; i < data.feed.entry.length; i++) { 
  
  step += 50;
  
  rankings.push(data.feed.entry[i].gsx$stephanedberg.$t)
  steps.push(step)  
  pairsArray.push([steps[i], rankings[i]*10]);

  var MyChart = hypeDocument.getElementById("myChart_path");
  
} 
 
 MyChart.setAttribute('d', 'M' + steps[0] + ' ' + rankings[0]*10 +' L '+ pairsArray);
  
  });

does this work? not sure if i got your request right … $.getJSON(“https://spreadsheets.google.com/feeds/list/xxxxxxxxxxx/od6/public/values?alt=json”, function(data) {

  var rankings = [];
  var steps = [];
  var pathPointsArray = [];
  var step = 0;
  var command = 'M ';
  
  for (i = 0; i < data.feed.entry.length; i++) { 
  
  step += 50;
  
  rankings.push(data.feed.entry[i].gsx$stephanedberg.$t)
  steps.push(step)  
  pathPointsArray.push(command + steps[i] +  ' ' + rankings[i]*10);
command = 'L ';
  
} 

var MyChart = hypeDocument.getElementById("myChart_path");

 MyChart.setAttribute('d', pathPointsArray.join(' '));
  
  });

Hi @h_classen, thank you!

Yes that works really well.
Next steps, working out how to do this for multiple lines without it being too manual and also how to duplicate this for motion paths.

I will report back here with findings.

NativePathMultiple.hype.zip (25.6 KB)

Updated the file. Three lines working pretty well. But all manually added at the moment.

$.getJSON("https://spreadsheets.google.com/feeds/list/xxxxxxxxxx/od6/public/values?alt=json", function(data) { 
  
  var edbergRankings = [];
  var edbergPointsArray = [];
  
  var beckerRankings = [];
  var beckerPointsArray = [];
   
  var agassiRankings = [];
  var agassiPointsArray = [];
  
  var steps = [];
  var step = 0;
  var stepSize = 49;
  var command = 'M ';
  
  for (i = 0; i < data.feed.entry.length; i++) { 
  
  step += stepSize;
  steps.push(step)
  
  edbergRankings.push(data.feed.entry[i].gsx$stephanedberg.$t)
  agassiRankings.push(data.feed.entry[i].gsx$andreagassi.$t)
  beckerRankings.push(data.feed.entry[i].gsx$borisbecker.$t)
    
  edbergPointsArray.push(command + steps[i] +  ' ' + edbergRankings[i]*10);
  agassiPointsArray.push(command + steps[i] +  ' ' + agassiRankings[i]*10);
  beckerPointsArray.push(command + steps[i] +  ' ' + beckerRankings[i]*10);
  
  command = 'L ';
  
} 

var Edberg = hypeDocument.getElementById("Edberg_path");
var Agassi = hypeDocument.getElementById("Agassi_path");
var Becker = hypeDocument.getElementById("Becker_path");

 Edberg.setAttribute('d', edbergPointsArray.join(' '));
 Agassi.setAttribute('d', agassiPointsArray.join(' '));
 Becker.setAttribute('d', beckerPointsArray.join(' '));
   
  });
2 Likes

nice :slight_smile:

NativePathMultipleV2.hype.zip (26.3 KB)

Got a rudimentary ‘follow’ to work. A purple circle is animated along with one of the lines, by calculating the line’s total length and then dividing it by current time in timeline (to get a sort of percentage) Total time is 10sec so I multiply that by ten to get 100. It’s ‘bumpy’ to say the least, but it does work.

 var Edberg = hypeDocument.getElementById("Edberg_path");
 var EdbergPic = hypeDocument.getElementById("EdbergPic");
 var EdbergPathLength = Math.floor(Edberg.getTotalLength());
 var prcnt = (EdbergPathLength / 100)*hypeDocument.currentTimeInTimelineNamed('Main Timeline')*10;
 
  var pt = Edberg.getPointAtLength(prcnt);
  pt.x = Math.round(pt.x)-10;
  pt.y = Math.round(pt.y)+44;
  
  EdbergPic.style.top = pt.y+'px';
  EdbergPic.style.left = pt.x+'px';

Maybe a better way would be to attach the circle to the endpoint of path.style.strokeDasharray or path.style.strokeDashoffset as it progresses through the animation?

OK! So this now works smoothly when run while dragging the “year” marker. Three circles attached to the end of their respective paths! But there must be an easier way of writing this… But at least it works!

Updated project file: NativePathMultipleV3.hype.zip (25.2 KB)

var BeckerPic = hypeDocument.getElementById('BeckerPic');
var EdbergPic = hypeDocument.getElementById('EdbergPic');
var AgassiPic = hypeDocument.getElementById('AgassiPic');

var  current_frame = hypeDocument.currentTimeInTimelineNamed('Main Timeline');
var  total_frames = hypeDocument.durationForTimelineNamed('Main Timeline');

var Becker = hypeDocument.getElementById('Becker_path');
var Edberg = hypeDocument.getElementById('Edberg_path');
var Agassi = hypeDocument.getElementById('Agassi_path');

var bl = Becker.getTotalLength();
var BeckerLength = bl;

var el = Edberg.getTotalLength();
var EdbergLength = el;

var al = Agassi.getTotalLength();
var AgassiLength = al;
    
    Becker.style.strokeDasharray = bl + ' ' + bl; 
    Becker.style.strokeDashoffset = bl;
    
    Edberg.style.strokeDasharray = el + ' ' + el; 
    Edberg.style.strokeDashoffset = el;
    
    Agassi.style.strokeDasharray = al + ' ' + al; 
    Agassi.style.strokeDashoffset = al;

   var progress = current_frame/total_frames;

   Becker.style.strokeDashoffset = Math.floor(BeckerLength * (1 - progress));
   Edberg.style.strokeDashoffset = Math.floor(EdbergLength * (1 - progress));
   Agassi.style.strokeDashoffset = Math.floor(AgassiLength * (1 - progress));
     
  var bpt = Becker.getPointAtLength(bl*progress);
  bpt.x = Math.round(bpt.x)-10;
  bpt.y = Math.round(bpt.y)+44;
  
  var ept = Edberg.getPointAtLength(el*progress);
  ept.x = Math.round(ept.x)-10;
  ept.y = Math.round(ept.y)+44;
  
  var apt = Agassi.getPointAtLength(al*progress);
  apt.x = Math.round(apt.x)-10;
  apt.y = Math.round(apt.y)+44;
  
  BeckerPic.style.top = bpt.y+'px';
  BeckerPic.style.left = bpt.x+'px';
  
  EdbergPic.style.top = ept.y+'px';
  EdbergPic.style.left = ept.x+'px';
  
  AgassiPic.style.top = apt.y+'px';
  AgassiPic.style.left = apt.x+'px';

Doing some experiments and wanted to share as well as ask for some advice…

NativePathMultipleV3 2.hype.zip (50.7 KB)

The attached Hype document has two scenes. The first pulls data from one sheet where the year is in a column and player names are in a row. The code for this is very long and I only added 4 player (I would need 131 to cover the history of the top ten).

So the second scene is a different approach with a different code. Essentially the table is flipped with player names in a column and year in a row. For some reason, at least to me this seemed easier to implement… also a lot shorter (apart from the list of years that can’t be numbers?!

var players = [];
var rankings = [];
var points = [];

	$.getJSON("https://spreadsheets.google.com/feeds/list/xxxxxxxxx/2/public/values?alt=json", function(data) {
	
  var steps = [];
  var step = -49;
  var stepSize = 49;
  var command = 'M ';
  
  for (j = 0; j < data.feed.entry.length; j++) { 
          
  step += stepSize;
  steps.push(step);
  
players.push(data.feed.entry[j].gsx$players.$t)
rankings.push(data.feed.entry[j].gsx$seventhree.$t, data.feed.entry[j].gsx$sevenfour.$t, data.feed.entry[j].gsx$sevenfive.$t, data.feed.entry[j].gsx$sevensix.$t, data.feed.entry[j].gsx$sevenseven.$t, data.feed.entry[j].gsx$seveneight.$t, data.feed.entry[j].gsx$sevennine.$t, data.feed.entry[j].gsx$eightzero.$t, data.feed.entry[j].gsx$eightzero.$t, data.feed.entry[j].gsx$eightone.$t, data.feed.entry[j].gsx$eighttwo.$t);
points.push(command + steps[j] +  ' ' + rankings[j]*20 + ' ' + (steps[j]+(stepSize - 10)) + ' ' + rankings[j]*20);  
command = 'L ';
  
  }
    
 for (i = 0; i < players.length; i++) {
 
  var player = hypeDocument.getElementById(players[i]+"_path");
  
  player.setAttribute('d', points);

  }
  
  });

But this is not working quite right. If I look in the console log, the same points are set for all the native vector lines. I somehow need to split the point array to apply only to individual players. Seems nearly there, but now I’m stuck.