0

I am making a website where I can enter data for golf course greens, and then analyze it. I am using express in my backend to route to my ejs pages. I am also using a mySQL database to store the data. When creating the charts I used chart.js from w3schools, which got me to create a script within the html code to insert the javascript code. But then realized that I would have to create a database connection in the ejs file (which seems inefficient and unsafe (creating multiple connections to DB)). I then spent ages, creating functions inside my js file with the database and express connections where I do all my other queries. I ended just trying to keep the script from website in the ejs file, and then try and pass the data from my js file into this ejs file to display. This is the reason that I'm asking for help. I can't figure out how to pass this list of data from my js file to my ejs file to use in a script? While, thinking about it. There must be a more efficient way of doing this?

EJS file where I am trying to display the graph:

<body>
    <div class="container">
        <div class="chart">
            <canvas id="myChart" style="width:100%;max-width:700px"></canvas>
        </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
    <script>
    document.addEventListener('DOMContentLoaded', function () {
            const scatterGraphData = JSON.parse(document.getElementById('myChart').dataset.scatterData);
            
            new Chart("myChart", {
                type: "scatter",
                data: {
                    datasets: [{
                        pointRadius: 4,
                        pointBackgroundColor: "rgb(0,0,255)",
                        data: scatterGraphData
                    }]
                },
                options: {
                    legend: { display: false },
                    scales: {
                        xAxes: [{ ticks: { min: 40, max: 160 } }],
                        yAxes: [{ ticks: { min: 6, max: 16 } }],
                    }
                }
            });
        });
        </script>
</body>

Function which calculates the data:

async function getChartData() {
    const now = new Date();
    const formattedDate = now.toISOString().split('T')[0];

    connection.query("SELECT * FROM sample WHERE Date = ?", [formattedDate], (error, results) => {
        if (error) {
            console.log("Database Error inside getChartData");
            return;
        }
        if (results.length === 0) {
            // No data found today
            console.log("Entered in the no data found");
            // random test data to see if graph even works
            const defaultData = [
                { x: 50, y: 7 },
                { x: 60, y: 8 },
                { x: 70, y: 8 },
                { x: 80, y: 9 },
                { x: 90, y: 9 },
                { x: 100, y: 9 },
                { x: 110, y: 10 },
                { x: 120, y: 11 },
                { x: 130, y: 14 },
                { x: 140, y: 14 },
                { x: 150, y: 15 }
            ];
            return defaultData;
        }
        // real Data exists to map
        let AveragesList = [];
        let counter = 0;
        const sampleHoles = [2, 12, 16];
        for (const holeid of sampleHoles) {
            const holeRecord = connection.query("SELECT * FROM holes WHERE Id = ?", [results[0][holeid]]);
            const upSlopeData = connection.query("SELECT * FROM upslopespeed WHERE UpSlopeID = ?", [holeRecord[0].UpSlopeSpeedID]);
            const downSlopeData = connection.query("SELECT * FROM downslopespeed WHERE DownSlopeID = ?", [holeRecord[0].DownSlopeSpeedID]);

            const backLeftSpeed = (upSlopeData[0].BackLeft * 2) / (upSlopeData[0].BackLeft + downSlopeData[0].BackLeft);
            const backCentreSpeed = (upSlopeData[0].BackCentre * 2) / (upSlopeData[0].BackCentre + downSlopeData[0].BackCentre);
            const backRightSpeed = (upSlopeData[0].BackRight * 2) / (upSlopeData[0].BackRight + downSlopeData[0].BackRight);

            const AverageSpeed = (backLeftSpeed + backCentreSpeed + backRightSpeed) / 3;
            AveragesList[counter] = AverageSpeed;
            counter++;
        }

        const xyValues = [
            {x:2, y:AveragesList[0]},
            {x:12, y:AveragesList[1]},
            {x:16, y:AveragesList[2]}
          ];

        return xyValues;
    });
}

This is function that is called when the page is to be loaded:

function DashboardDetermine(req, res) {
    if (req.session.user.isAdmin === 1) {
        // ADMIN
        // get all employees to list on admin dashboard
        connection.query("SELECT * FROM user WHERE admin = 0", async (error, results) => {
            if (error) {
                console.error('Error executing query:', error);
                req.flash('error_msg', 'An error occurred while loading users.');
                res.redirect('/dashboard');
                return;
            }
            const scatterGraphData = await JSON.stringify(getChartData());
            console.log(scatterGraphData);
            // Pass the results to the template
            res.render('dashboardAdmin', { user: results, scatterGraphData });
        });
    } else {
        // EMPLOYEE
        res.render("dashboard");
    }
}

ChatGPT suggested to format it as a JSON and then pass that when rendering the file. Not sure if this changed anything. Before I just passed the getChartData() directly. Like this res.render('dashboardAdmin', { user: results, scatterData: getChartData()}); Which didn't work.

On a day which is empty the getChartData() function should just return the fixed data which I took from the w3schools website in their example just to eliminate sources of error, it seems to get data from the getChartData() call, but the JSON file from the console.log returns {}. Which causes the chart to not appear.

This is why I am wondering whether the move the scatter graph script into the same into this JS file so I can have the functions enter the data there?

Hope this not to confusing to understand, many thanks for any help!

2
  • Ensure that the data fetched from MySQL has the appropriate structure for your chart. Adjust the labels and values extraction in the EJS template as per your data structure. Commented Jul 10 at 4:57
  • Thanks, will do. Was just trying to get a basic working version. Commented Jul 10 at 9:49

1 Answer 1

1

A few observations, comments and also a working code based on your code enclosed.

a. The second argument to res.render basically is an object. It will then be available in the template as an object itself. Please see in the code below as it has been passed as an object, more specifically in this case as - an object of array of objects.

  res.render('index', { graphdata });

b. Continuing the above point, there is no mandate that it must be in JSON format. Formatting the same entirely depends on the context in which it will be used in the template. Please also note that the Chart constructor function directly accepts javascript object, it does note need to be JSON formatted. This you may be able to test and confirm by assigning the array of objects directly to its property data.datasets.data.

c. However the below code has used JSON format. This has come as a technical requirement. The object passed into the template will be available as an object itself which has already mentioned in point a, however accessing the same in JavaScript in the 'script' tag would require a little more work. The following statement does the job. In this particular case, it first JSON formatted, and thus made available in the JavaScript scripting context, finally reverted the JSON format by parsing. By these steps, it has become the same object type passed from the server. This is one of the technical ways to get an object passed from the server made accessible in the scripting context as well. Please also note the hyphon in the ejs tag '<%-', it is also required to get the unescaped values.

const graphdata = JSON.parse('<%- (JSON.stringify(graphdata)) %>');

A working code:

server.js

const express = require('express');
const app = express();

app.use(express.static('./'));

app.engine('ejs', require('ejs').__express);
app.set('views', './');
app.set('view engine', 'ejs');

const graphdata = [
  { x: 50, y: 7 },
  { x: 60, y: 8 },
  { x: 70, y: 8 },
  { x: 80, y: 9 },
  { x: 90, y: 9 },
  { x: 100, y: 9 },
  { x: 110, y: 10 },
  { x: 120, y: 11 },
  { x: 130, y: 14 },
  { x: 140, y: 14 },
  { x: 150, y: 15 },
];
console.log(graphdata);
app.get('/', (req, res) => {
  res.render('index', { graphdata });
});

app.listen(3000, () => console.log('L@3000'));

index.ejs

<!DOCTYPE html>
<html>
  <head>
    Chart.js sample - Scatter chart
  </head>
  <body>
    <div class="container">
      <div class="chart">
        <canvas id="myChart" style="width: 100%; max-width: 700px"></canvas>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
    <script>
      document.addEventListener('DOMContentLoaded', function () {
        const graphdata = JSON.parse('<%- (JSON.stringify(graphdata)) %>');

        new Chart('myChart', {
          type: 'scatter',
          data: {
            datasets: [
              {
                pointRadius: 4,
                pointBackgroundColor: 'rgb(0,0,255)',
                data: graphdata,
              },
            ],
          },
          options: {
            legend: { display: false },
            scales: {
              xAxes: [{ ticks: { min: 40, max: 160 } }],
              yAxes: [{ ticks: { min: 6, max: 16 } }],
            },
          },
        });
      });
    </script>
  </body>
</html>

Output: Scatter chart

Citations:

Accessing passed EJS variable in Javascript file

Update : 11th Jul 24

step by step 1 - object in template step by step 2 - object in template step by step 3 - object in template

6
  • Thank you for your confirmation. Please do revert in case any clarifications in the code you may require. Commented Jul 10 at 9:48
  • Thank you so much! This works really well. I'm still slightly confused about the JSON formatting. I did a little test inside the <script> tags console.log("The data straight up: " + '<%- scatterGraphData %>') const graphData = JSON.parse('<%- (JSON.stringify(scatterGraphData)) %>') console.log("Data after conversion " + graphData); and in the console of the browser they both return the same data. [object object], .... So, im confused what difference the JSON formatting does? (But it only works with the JSON formatting) Commented Jul 10 at 9:52
  • No issues, it is a very common source for confusion. We shall work on this. Please give me some time, may be by tomorrow, I shall add another update to the same answer, showing step by step how that part is working. Commented Jul 10 at 10:04
  • I'm pretty sure I've figured it out. I read online that doing it directly, EJS injects the data into html which loses the formatting of the javascript objects. The reason the data outputs the same when I console.log both of them is because the formatting has to do with the actual objects, not the list. I think that's right? Commented Jul 10 at 11:44
  • Yes that is the fact, you have got it. To make the answer to this question more useful , I have documented the same and updated the answer. If required, you can see it under the section "update 11th Jul 24". Commented Jul 11 at 4:14

Not the answer you're looking for? Browse other questions tagged or ask your own question.