I want to create multiple line graphs with their own datasets into one p5.js sketch.
I created one line graph and do not know how to make another line graph to work with a different dataset.
https://editor.p5js.org/ariel.koh/sketches/VsRk3KxYp
I tried to create another preload table for my second graph but it did not work so I am still confused.
This was what I tried but I think it is not right, I never tried working with multiple datasets before so I am confused :(
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
// canonattacks dataset
canonaattacksdataset = loadTable("canonattacks.csv", "csv",
"header");
}
You're on the right track loading the data.
The p5.js code you posted is a bit convoluted, here only a few examples:
the year labels a manually drawn, for each chart (even though the data is contained in the csv files)
if the text is simply drawn for the years but separate style, coordinate transformations, etc. are used then the push()/pop() call don't add anything.(e.g push();let sbm = 'Saul Bass Movies';...)
the drawLineGraph() function is defined twice (which might get you into variable shadowing bugs) and might be accidentally called within itself
No wonder you're confused.
I'd simplify as much as possible:
get back to loading and displaying one graph
load the second dataset (as you already correctly do above)
display the second graph
For example:
// file -> duplicate and file -> save
// this sketch first before you
// start working on it.
//
//
//
// Graph from Data, Line graph example
//
// Slides:
// http://slides.com/sojamo/cid-3-2122/fullscreen#/12/5
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
canonaattacksdataset = loadTable("canonattacks.csv", "csv", "header");
}
function setup() {
// if you add SVG as 3rd parameter and then
// press s, the canvas will be saved as SVG file
createCanvas(displayWidth, displayHeight, SVG);
}
function draw() {
background(184, 37, 0);
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text("Burns", 30, 20, 330, 150); // Text wraps within text box
pop();
//linegraph
drawLineGraph({
dataset: burnsdataset,
column: 1,
x: 30,
y: 50,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text("Canon Attacks", 30, 250, 330, 150); // Text wraps within text box
pop();
//linegraph
drawLineGraph({
dataset: canonaattacksdataset,
column: 1,
x: 30,
y: 280,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
}
function drawLineGraph(theProps = {}) {
let values = theProps.dataset.getColumn(theProps.column);
let minValue = min(values);
let maxValue = max(values);
let x = theProps.x || 0;
let y = theProps.y || 0;
let w = theProps.w || 400;
let h = theProps.h || 300;
let bg = theProps.bg || color(255, 40);
let fg = theProps.fg || color(255, 120);
let dc = theProps.dc || color(0, 120);
let bc = theProps.bc || color(0, 120);
let lc = theProps.lc || color(255);
let thickness = theProps.thickness || 1;
let isLabel = theProps.isLabel || false;
let isCurve = theProps.isCurve || false;
let isDots = theProps.isDots || false;
let isBackground = theProps.isBackground || false;
let spacing = w / (values.length - 1);
let marginTopBottom = 20;
// we will calcualte the x and y values for each
// point first, because we need it later a couple
// of times.
let xx = [];
let yy = [];
let n = values.length;
for (let i = 0; i < n; i++) {
xx[i] = i * spacing;
yy[i] = map(
values[i],
minValue,
maxValue,
-marginTopBottom,
marginTopBottom - h
);
}
push();
translate(x, y);
translate(0, h);
// 1. draw background
if (isBackground == true) {
fill(bg);
rect(0, 0, w, -h);
// 2. draw grid
noStroke();
for (let i = 0; i < n - 1; i++) {
// fill(255,i%2 == 0 ? 80:40);
rect(xx[i], 0, 1, -h);
}
}
// 3. draw curved line graph
noFill();
stroke(lc);
strokeWeight(thickness);
beginShape();
if (isCurve == true) {
vertex(xx[0], yy[0]);
}
for (let i = 0; i < n; i++) {
let fn = isCurve == true ? curveVertex : vertex;
fn(xx[i], yy[i]);
}
if (isCurve == true) {
vertex(xx[n - 1], yy[n - 1]);
}
endShape();
// 4. draw dot / label for each value
noStroke();
// finally lets draw the dots and
// labels if enabled
let d = thickness + 8;
for (let i = 0; i < n; i++) {
push();
translate(xx[i], yy[i]);
if(isDots == true) {
fill(bc);
ellipse(0, 0, d + 4, d + 4);
fill(dc);
ellipse(0, 0, d, d);
}
if (isLabel == true) {
fill(lc)
rotate(-0.5);
textSize(7);
text(values[i], d, -d);
}
pop();
}
pop();
}
With that working you can focus on making the code nicer.
Taking the idea of "don't repeat yourself(DRY)" you can notice a few patterns that could be improved:
you try to not just draw the graph, but also also display the graph's
label: perhaps the drawLineGraph() could include that
you seem to manually be drawing every single year label, even though the data is already in the dataset and the x position is already calculated to display the points of the graph
Tackling point 1 is fairly straight foward: simply move the label drawing instrunctions inside the function, handle the extra argument to get the label text and adjust coordinates so they're relative to the graph.
Here's a modified version of the above integrating label as part of the drawLineGraph() function:
// file -> duplicate and file -> save
// this sketch first before you
// start working on it.
//
//
//
// Graph from Data, Line graph example
//
// Slides:
// http://slides.com/sojamo/cid-3-2122/fullscreen#/12/5
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
canonaattacksdataset = loadTable("canonattacks.csv", "csv", "header");
}
function setup() {
// if you add SVG as 3rd parameter and then
// press s, the canvas will be saved as SVG file
createCanvas(displayWidth, displayHeight, SVG);
}
function draw() {
background(184, 37, 0);
//linegraph
drawLineGraph({
dataset: burnsdataset,
label: "Burns",
column: 1,
x: 30,
y: 50,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
//linegraph
drawLineGraph({
dataset: canonaattacksdataset,
label: "Canon Attacks",
column: 1,
x: 30,
y: 280,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
}
function drawLineGraph(theProps = {}) {
let values = theProps.dataset.getColumn(theProps.column);
let minValue = min(values);
let maxValue = max(values);
let x = theProps.x || 0;
let y = theProps.y || 0;
let w = theProps.w || 400;
let h = theProps.h || 300;
let bg = theProps.bg || color(255, 40);
let fg = theProps.fg || color(255, 120);
let dc = theProps.dc || color(0, 120);
let bc = theProps.bc || color(0, 120);
let lc = theProps.lc || color(255);
let thickness = theProps.thickness || 1;
let isLabel = theProps.isLabel || false;
let isCurve = theProps.isCurve || false;
let isDots = theProps.isDots || false;
let isBackground = theProps.isBackground || false;
let label = theProps.label || "";
let spacing = w / (values.length - 1);
let marginTopBottom = 20;
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text(label, x, y + h + marginTopBottom * 0.5, w, h); // Text wraps within text box
pop();
// we will calculate the x and y values for each
// point first, because we need it later a couple
// of times.
let xx = [];
let yy = [];
let n = values.length;
for (let i = 0; i < n; i++) {
xx[i] = i * spacing;
yy[i] = map(
values[i],
minValue,
maxValue,
-marginTopBottom,
marginTopBottom - h
);
}
push();
translate(x, y);
translate(0, h);
// 1. draw background
if (isBackground == true) {
fill(bg);
rect(0, 0, w, -h);
// 2. draw grid
noStroke();
for (let i = 0; i < n - 1; i++) {
// fill(255,i%2 == 0 ? 80:40);
rect(xx[i], 0, 1, -h);
}
}
// 3. draw curved line graph
noFill();
stroke(lc);
strokeWeight(thickness);
beginShape();
if (isCurve == true) {
vertex(xx[0], yy[0]);
}
for (let i = 0; i < n; i++) {
let fn = isCurve == true ? curveVertex : vertex;
fn(xx[i], yy[i]);
}
if (isCurve == true) {
vertex(xx[n - 1], yy[n - 1]);
}
endShape();
// 4. draw dot / label for each value
noStroke();
// finally lets draw the dots and
// labels if enabled
let d = thickness + 8;
for (let i = 0; i < n; i++) {
push();
translate(xx[i], yy[i]);
if(isDots == true) {
fill(bc);
ellipse(0, 0, d + 4, d + 4);
fill(dc);
ellipse(0, 0, d, d);
}
if (isLabel == true) {
fill(lc)
rotate(-0.5);
textSize(7);
text(values[i], d, -d);
}
pop();
}
pop();
}
And here's a modified version of the above that renders the labels on the x axis (year):
// file -> duplicate and file -> save
// this sketch first before you
// start working on it.
//
//
//
// Graph from Data, Line graph example
//
// Slides:
// http://slides.com/sojamo/cid-3-2122/fullscreen#/12/5
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
// canon attacks dataset
canonaattacksdataset = loadTable("canonattacks.csv", "csv", "header");
}
function setup() {
// if you add SVG as 3rd parameter and then
// press s, the canvas will be saved as SVG file
createCanvas(displayWidth, displayHeight, SVG);
drawPlots();
}
function drawPlots() {
background(184, 37, 0);
// burnsdataset linegraph
drawLineGraph({
dataset: burnsdataset,
label: "Burns",
xColumn: 0,
yColumn: 1,
x: 30,
y: 50,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
// canonaattacksdataset linegraph
drawLineGraph({
dataset: canonaattacksdataset,
label: "Canon Attacks",
xColumn: 0,
yColumn: 1,
x: 30,
y: 300,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
}
function drawLineGraph(theProps = {}) {
let values = theProps.dataset.getColumn(theProps.yColumn);
let minValue = min(values);
let maxValue = max(values);
let labels = theProps.dataset.getColumn(theProps.xColumn);
let x = theProps.x || 0;
let y = theProps.y || 0;
let w = theProps.w || 400;
let h = theProps.h || 300;
let bg = theProps.bg || color(255, 40);
let fg = theProps.fg || color(255, 120);
let dc = theProps.dc || color(0, 120);
let bc = theProps.bc || color(0, 120);
let lc = theProps.lc || color(255);
let thickness = theProps.thickness || 1;
let isLabel = theProps.isLabel || false;
let isCurve = theProps.isCurve || false;
let isDots = theProps.isDots || false;
let isBackground = theProps.isBackground || false;
let label = theProps.label || "";
let spacing = w / (values.length - 1);
let marginTopBottom = 20;
// y position for labels (e.g. years)
let labelsY = marginTopBottom * 0.5;
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text(label, x, y - marginTopBottom, w, h); // Text wraps within text box
pop();
// we will calculate the x and y values for each
// point first, because we need it later a couple
// of times.
let xx = [];
let yy = [];
let n = values.length;
for (let i = 0; i < n; i++) {
xx[i] = i * spacing;
yy[i] = map(
values[i],
minValue,
maxValue,
-marginTopBottom,
marginTopBottom - h
);
}
push();
translate(x, y);
translate(0, h);
// 1. draw background
if (isBackground == true) {
fill(bg);
rect(0, 0, w, -h);
// 2. draw grid
noStroke();
for (let i = 0; i < n - 1; i++) {
// fill(255,i%2 == 0 ? 80:40);
rect(xx[i], 0, 1, -h);
}
}
// 3. draw curved line graph
noFill();
stroke(lc);
strokeWeight(thickness);
beginShape();
if (isCurve == true) {
vertex(xx[0], yy[0]);
}
for (let i = 0; i < n; i++) {
let fn = isCurve == true ? curveVertex : vertex;
fn(xx[i], yy[i]);
// label per data point
push();
noStroke();
fill(255);
textSize(9);
textAlign(CENTER);
text(labels[i], xx[i], labelsY);
pop();
}
if (isCurve == true) {
vertex(xx[n - 1], yy[n - 1]);
}
endShape();
// 4. draw dot / label for each value
noStroke();
// finally lets draw the dots and
// labels if enabled
let d = thickness + 8;
for (let i = 0; i < n; i++) {
push();
translate(xx[i], yy[i]);
if(isDots == true) {
fill(bc);
ellipse(0, 0, d + 4, d + 4);
fill(dc);
ellipse(0, 0, d, d);
}
if (isLabel == true) {
fill(lc)
rotate(-0.5);
textSize(7);
text(values[i], d, -d);
}
pop();
}
pop();
}
// if you want to use the SVG export
// option, go to setup and enable SVG mode
function keyPressed() {
if (key === "s") {
saveSVG("line-graph.svg");
}
}
There's one slight detail snuck in there: I've renamed draw() to drawPlots() and called it once from setup(). That is to render the plots a single time since they don't change and save CPU resources. If you do plan to have some sort interation changing these graphs then you should use draw().
I've made a square grid on top of the canvas and also a cruve (that is meant to have a fading trail). I made them seperately and tried combining them so the curve would appear on top of the grid. However, it doesn't show the curve.
I've commented out the grid so it's easier to see the curve.
How do I get this to work?
var cols = 10;
var rows = 10;
var t = 0;
var particleArray = [];
function setup() {
createCanvas(600, 600);
background(0);
fill(100);
rect(0, 0, 550, 550, 25);
}
// blue grid
function draw() {
/*for (var c = 0; c < cols; c++) {
for (var r = 0; r < rows; r++) {
var XO = 25 + c * 50;
var YO = 25 + r * 50;
stroke(0);
fill(100,149,237);
rect(XO, YO, 50, 50);
noLoop();
// :(
}
}
*/
//curve
y = width / 2 + 270 * sin(3 * t + PI / 2) - 25;
x = height / 2 + 270 * sin(1 * t) - 25;
particleArray.push(new Particle(x, y, t));
for (i=0; i<particleArray.length; i++) {
particleArray[i].show(t);
}
if (particleArray.length > 700) {
particleArray.shift();
}
t += .01;
}
function Particle(x, y, t) {
this.x = x;
this.y = y;
this.t = t;
this.show = function(currentT) {
var _ratio = t / currentT;
_alpha = map(_ratio, 0, 1, 0, 255); //points will fade out as time elaps
fill(255, 255, 255, _alpha);
ellipse(x, y, 5, 5);
}
}
I don't know if this was intentional but you called noLoop() function where you're drawing the grid. If you comment that out it works.
I have a simple code that traces the Liss cruve with a small ellipse. I was wondering how to add a fading trail to this shape so it represents the cruve more clearly. I only know a bit about adding trails that follows the mouse but I'm not sure how to do this one.
Any help is appreciated, here is the code:
var t = 0;
function setup() {
createCanvas(500, 500);
fill(255);
}
function draw() {
background(0);
for (i = 0; i < 1; i++) {
y = 160*sin(3*t+PI/2);
x = 160*sin(1*t);
fill(255);
ellipse(width/2+x, height/2+y, 5, 5);
t += .01;
}
}
Try changing background(0) to background(0, 0, 0, 4) :)
Here is a working example:
https://editor.p5js.org/chen-ni/sketches/I-FbLFDXi
Edit:
Here is another solution that doesn't use the background trick:
https://editor.p5js.org/chen-ni/sketches/HiT4Ycd5U
Basically, it keeps track of each point's position and redraws them in every frame with updated alpha to create the "fading out" effect.
var t = 0;
var particleArray = [];
function setup() {
createCanvas(500, 500);
}
function draw() {
background(0);
y = width / 2 + 160 * sin(3 * t + PI / 2);
x = height / 2 + 160 * sin(1 * t);
particleArray.push(new Particle(x, y, t));
for (i=0; i<particleArray.length; i++) {
particleArray[i].show(t);
}
//keep the array short, otherwise it runs very slow
if (particleArray.length > 800) {
particleArray.shift();
}
t += .01;
}
function Particle(x, y, t) {
this.x = x;
this.y = y;
this.t = t;
this.show = function(currentT) {
var _ratio = t / currentT;
_alpha = map(_ratio, 0, 1, 0, 255); //points will fade out as time elaps
fill(255, 255, 255, _alpha);
ellipse(x, y, 5, 5);
}
}
I've lately been using parcel.js with react apps and it's been very user friendly.
I'm following along to a Coding Train tutorial and parcel for whatever reason is not running the code.
https://github.com/CodingTrain/website/tree/master/CodingChallenges/CC_001_StarField/P5
When I run this with live server, no errors and everything runs correctly.
When I attempt to run it with parcel.js, there are no errors, it just doesn't run, nothing renders.
Any idea why this might be happening?
Thank you
I think the issue here is that Parcel.js doesn't put variables defined in src'd scripts into the global scope by default, and p5.js depends on these globals by default (setup and draw in particular). You could solve the problem by making speed, setup and draw in sketch.js, and Star in Star.js, properties of window (or, equivalently, global). See the example below, which works when built with parcel index.html.
(This method has the benefit of preserving access to the familiar (global) p5.js commands without the need for a prefix, but might give complications with globals being overwritten if you're using other libraries.)
//sketch.js
let stars = [];
window.speed = undefined;
window.setup = function setup() {
createCanvas(600, 600);
for (let i = 0; i < 800; i++) {
stars[i] = new Star();
}
}
window.draw = function draw() {
speed = map(mouseX, 0, width, 0, 50);
background(0);
translate(width / 2, height / 2);
for (let i = 0; i < stars.length; i++) {
stars[i].update();
stars[i].show();
}
}
//Star.js
window.Star = function Star() {
this.x = random(-width, width);
this.y = random(-height, height);
this.z = random(width);
this.pz = this.z;
this.update = function() {
this.z = this.z - speed;
if (this.z < 1) {
this.z = width;
this.x = random(-width, width);
this.y = random(-height, height);
this.pz = this.z;
}
};
this.show = function() {
fill(255);
noStroke();
var sx = map(this.x / this.z, 0, 1, 0, width);
var sy = map(this.y / this.z, 0, 1, 0, height);
var r = map(this.z, 0, width, 16, 0);
ellipse(sx, sy, r, r);
var px = map(this.x / this.pz, 0, 1, 0, width);
var py = map(this.y / this.pz, 0, 1, 0, height);
this.pz = this.z;
stroke(255);
line(px, py, sx, sy);
};
}
<html>
<head>
<meta charset="UTF-8" />
<title>StarField_p5.js</title>
<style>
body {
padding: 0;
margin: 0;
}
canvas {
vertical-align: top;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/p5#1.0.0/lib/p5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/p5#1.0.0/lib/addons/p5.sound.min.js"></script>
<script src="./Star.js"></script>
<script src="./sketch.js"></script>
</head>
<body>
<main></main>
</body>
</html>
Alternatively you could use instance mode of P5.js to set things up without globals (See also thread here - the code sample of which has now had its P5.js dependency removed, but you can look at the commit history). This has the downside that the familiar P5.js / processing functions have to be prefixed with an object reference, minimally something like s.createCanvas (the methods often seem to depend on being bound properly in object orientated style). It is likely the safer approach if other libraries are involved though, especially as p5.js defines lots of globals with common names like random, map and height if it isn't used in instance mode.
Something like this:
// index.js
// I've combined sketch.js and Star.js here to make
// it easier to share variable speed
const myp5 = new p5((s) => {
const stars = [];
let speed =1;
s.setup = function setup() {
s.createCanvas(600, 600);
for (let i = 0; i < 800; i++) {
stars[i] = new Star();
}
};
s.draw = function draw() {
speed = s.map(s.mouseX, 0, s.width, 0, 50);
s.background(0);
s.translate(s.width / 2, s.height / 2);
for (let i = 0; i < stars.length; i++) {
stars[i].update();
stars[i].show();
}
};
function Star() {
this.x = s.random(-s.width, s.width);
this.y = s.random(-s.height, s.height);
this.z = s.random(s.width);
this.pz = this.z;
this.update = function () {
this.z = this.z - speed;
if (this.z < 1) {
this.z = s.width;
this.x = s.random(-s.width, s.width);
this.y = s.random(-s.height, s.height);
this.pz = this.z;
}
};
this.show = function () {
s.fill(255);
s.noStroke();
var sx = s.map(this.x / this.z, 0, 1, 0, s.width);
var sy = s.map(this.y / this.z, 0, 1, 0, s.height);
var r = s.map(this.z, 0, s.width, 16, 0);
s.ellipse(sx, sy, r, r);
var px = s.map(this.x / this.pz, 0, 1, 0, s.width);
var py = s.map(this.y / this.pz, 0, 1, 0, s.height);
this.pz = this.z;
s.stroke(255);
s.line(px, py, sx, sy);
};
}
},'sketch1');
<html>
<head>
<meta charset="UTF-8" />
<title>StarField_p5.js</title>
<style>
body {
padding: 0;
margin: 0;
}
canvas {
vertical-align: top;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/p5#1.0.0/lib/p5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/p5#1.0.0/lib/addons/p5.sound.min.js"></script>
<script src="./index.js"></script>
</head>
<body>
<div id="sketch1"></div>
</body>
</html>