Displaying text from an API inside the canvas

Hi, I’m having trouble getting the text from an API to display on my canvas, I can get it outside of it but I can’t find the way to put it in. I have the API key in my code and I know the variable names are kinda weird. My end goal is tho display the content of 5 news stories with the provider, time, title, description and image in the canvas and then making an image of each one (I think with createImage()) so I can apply somo kind of algorithm to them. I’ve been trying to do this for a couple of days but can’t find the way to do it.

let url = 'https://api.jornalia.net/api/v1/articles?apiKey=?&providers=Clarin%2CTN%2CPagina12%2CLaNacion%2CInfobae&categories=POLITICA%2CECONOMIA%2CSOCIEDAD'

let h4, h5, h1, P, img;

function setup(){

  loadJSON(url, gotData, 'json');

  createCanvas(windowWidth, windowHeight);

  background(150);

}

function gotData(data){

  for ( let i = 0; i <= 4; i++){

    h4 = data.articles[i].provider.name;

    h5 = data.articles[i].publishedAt;

    h1 =  data.articles[i].title;

    P = data.articles[i].description;

    img = data.articles[i].imageUrl;

  }

}

function draw(){

  let news = createElement('h4', h4);

  text(news,20,20);

}

You need to call the function

Make news a global variable and in gotdata fill it with your data

It is passed as the second parameter to loadJSON which is the callback function for once data is loaded. So it does not need to be called directly.

I can’t see where the parameter data is initialised. Does that mean it is undefined when you call gotData?

There are a few issues going on, but first and foremost is the usage of the text() function. The text function in p5.js expects a string for its first parameter, not an p5.Element which is what createElement() returns. If you want to format text that you render to the canvas with text() you need to use methods like textSize() / textStyle() / textFont().

Other issues include:

  • You should avoid calling createElement and the like in draw() unless you disable looping with noLoop() unlike text drawn to the canvas, HTML elements created this way are persistent so this would be a huge memory leak.
  • Your for loop keeps overwriting all your variables, so you’ll only really capture the data from the 5th article (the one with index 4).
  • Calls to draw that happen before the data is loaded will fail because h4 (and the other variables containing article data) have not yet been initialized. Once a call to draw fails, p5.js stops calling it.

Here’s a working example:

let url = 'https://www.paulwheeler.us/files/example.json';

let h4 = '', h5, h1, P, img;

function setup() {
  createCanvas(windowWidth, windowHeight);
  loadJSON(url, gotData, 'json');
}

function gotData(data){
  print('gotData');
  console.log(data);
  
  for (let i = 0; i < data.articles.length; i++) {
    // This is going to only store the last article
    // information in these variables
    h4 = data.articles[i].provider.name;

    h5 = data.articles[i].publishedAt;

    h1 =  data.articles[i].title;

    P = data.articles[i].description;

    img = data.articles[i].imageUrl;
  }
  
  // This creates an HTML element that is separate from your canvas
  let news = createElement('h4', h4);
  
  // By default, an h4 element will be a block element added after your canvas.
  // The position function changes the element to be positioned absolutely
  news.position(0,0);
}

function draw() {
  background(255);
  // The p5.js text function can only take a string.
  // It cannot take an HTML element. if you want HTML-like
  // formatting and layout you either need to use HTML or
  // implement it yourself with p5.js drawing commands.
  text(h4, width / 2, height / 2);
}
1 Like

Thank you very much! I kept working on it and could put the text inside the canvas but now I’m havieng other problems, I wonder if you’d know how to fix them. First I can’t seem to find a way to put the image below the texI tried changing the img variable, I thought by putting the ’ ’ inside it it would grab the url of the image and load it in in the loadImage function but it didn’t work. The other problem is I don’t know how to display those 4 different sets of texts and images on draw. I tried putting another for loop but couldn’t make it work.

let url = 'https://api.jornalia.net/api/v1/articles?apiKey=?&providers=Clarin%2CTN%2CPagina12%2CLaNacion%2CInfobae&categories=POLITICA%2CECONOMIA%2CSOCIEDAD'

let prov = '', time= '', title = '', desc = '', img = '';

function setup(){

  loadJSON(url, gotData, 'json');

  createCanvas(windowWidth, windowHeight);

}

function gotData(data){

  for ( let i = 0; i < 4; i++){

   prov= data.articles[i].provider.name;

   time = data.articles[i].publishedAt;

   title =  data.articles[i].title;

   desc = data.articles[i].description;

   img = data.articles[i].imageUrl;

  }

}

function draw(){

  background(150);

  for( j = 0; j < 4; j++){

   textSize(16);

   text(prov,10,20);

   textSize(12);

   text(time,10,40);

   textSize(30);

   text(title,10,70);

   textSize(16);

   text(desc,10,100);

   loadImage(img);

   image(img,10,120);

  }

}

Ok, first issue: drawing images.

  1. loadImage isn’t instantaneous, so you need to call it and then it will take some time before the image is available
  2. you need to store the result (a p5.Image instance) returned from loadImage in a variable and then pass that p5.Image to the image function.
  3. You don’t want to call loadImage in every call to draw, you want to call it once and reuse the result in the draw function.

I would do something like this in gotData:

		imgSrc = data.articles[i].imageUrl;
		img = loadImage(
			imgSrc,
			() => {
				imgLoaded = true;
			}
		);

And something like this in your draw function:

	if (imgLoaded) {
		image(img, 10, 120);
	} else {
		text('Loading...', 10, 120);
	}

thank you so much! I really appreciate all the help! I’ll keep working on it

Regarding the second issue: displaying multiple articles, the problem is that you are trying to store multiple values (one for each article) in individual variables (i.e. prov, time, title) etc. What you need is arrays which you add items to (or better yet a single array to which you add an object containing the article data). Then you can use a for loop in your draw function to draw each article. However, you also don’t want to draw the articles over top of one another. So you need to determine how you want to arrange the articles and then draw them in different places. The easiest way to accomplish the latter (drawing in different places) is with push()/translate()/pop() (recommended viewing: Transformations (Translate, Rotate, Push/Pop) - Part 1 - Additional Topics Tutorial #9.1 · The Coding Train). Here’s a functional example:

let url = 'https://www.paulwheeler.us/files/example2.json';

let articles = [];

function setup() {
  createCanvas(windowWidth, windowHeight);
  loadJSON(url, gotData, 'json');
}

function gotData(data) {
  print('gotData');
  console.log(data);

  for (let i = 0; i < data.articles.length; i++) {
    let content = {
      prov: data.articles[i].provider.name,
      time: data.articles[i].publishedAt,
      title: data.articles[i].title,
      desc: data.articles[i].description,
      imgSrc: data.articles[i].imageUrl,
      imgLoaded: false
    };

    content.img = loadImage(
      imgSrc,
      () => {
        content.imgLoaded = true;
      }
    );

    articles.push(content);
  }
}

function draw() {
  background(255);

  // Simple hack: assume each article fits in a 200px x 200px sqaure
  let articlesPerRow = floor(width / 200);

  for (let i = 0; i < articles.length; i++) {
    // Save the current drawing position
    push();

    // determine layout position
    let y = floor(i / articlesPerRow) * 200;
    let x = (i % articlesPerRow) * 200;

    // translate to the top left corner of the current article's grid spot
    translate(x, y);

    textSize(16);
    text(articles[i].prov, 10, 20);
    textSize(12);
    text(articles[i].time, 10, 40);
    textSize(30);
    text(articles[i].title, 10, 70);
    textSize(16);
    text(articles[i].desc, 10, 100);

    if (articles[i].imgLoaded) {
      image(articles[i].img, 10, 120);
    } else {
      text('Loading...', 10, 120);
    }

    // Revert back to the previous drawing position
    pop();
  }
}
1 Like

I really can’t thank you enough, I’ve been trying a lot of things this past couple of days but couldn’t do it, thanks for all the advice! Recently I’ve been watching a lot of videos by the coding train too, I’ll give that one a watch!

1 Like