skip to Main Content

I have an array and I want to create below HTML table from the array

enter image description here

I’ve tried writing the code bleow but seems like its appending rows in different places
Is there any other way to archive above table. im basically trying to create like Gannt chart kind of table

// Extract unique years and months
var years = [...new Set(dataArray.map(item => item.StartYear))];
var months = [...new Set(dataArray.map(item => item.StartMonth).concat(dataArray.map(item =>
  item.EndMonth)))];

// Sort years and months
years.sort();
months.sort();

// Create the table dynamically
var tableHTML = '<table>';
tableHTML += '<tr><th>Year</th>';
years.forEach(year => tableHTML += `<th colspan="${months.length}">${year}</th>`);
tableHTML += '</tr>';
tableHTML += '<tr><td>Month</td>';
months.forEach(month => tableHTML += `<td>${month}</td>`);
tableHTML += '</tr>';

dataArray.forEach(item => {
  tableHTML += `<tr><td>${item.Project}</td>`;
  years.forEach(year => {
    months.forEach(month => {
      var isInProjectRange = (year === item.StartYear && month >= item.StartMonth && month <=
          item.EndMonth) ||
        (year === item.EndYear && month <= item.EndMonth);
      tableHTML += `<td>${isInProjectRange ? 'YES' : ''}</td>`;
    });
  });
  tableHTML += '</tr>';
});

tableHTML += '</table>';

// Append the table to the div with id 'table-container'
document.getElementById('table-container').innerHTML = tableHTML;
<div id="table-container"></div>

<script>
var dataArray = [
    {'Project': 'Project 1', 'StartYear': '2023', 'EndYear': '2023', 'StartMonth': 'Nov', 'EndMonth': 'Dec', 'Owner':'Tom'},
    {'Project': 'Project 1.1', 'StartYear': '2023', 'EndYear': '2023', 'StartMonth': 'Nov', 'EndMonth': 'Dec', 'Owner':'Tom'},
    {'Project': 'Project 2', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Jan', 'EndMonth': 'May', 'Owner':'Tom'},
    {'Project': 'Project 2.1', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Jan', 'EndMonth': 'Feb', 'Owner':'Tom'},
    {'Project': 'Project 2.2', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Feb', 'EndMonth': 'Mar', 'Owner':'Tom'},
    {'Project': 'Project 2.3', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Mar', 'EndMonth': 'May', 'Owner':'Tom'}
     ];

</script>

2

Answers


  1. Here is a snippet that demonstrates one possible way of doing it:

    // Extract unique years and months
    const months = "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),
          midx   = months.reduce((a,m,i)=>(a[m]=i,a),{}),
          proj    = dataArray.map(p=>[p.Project,100*p.StartYear+midx[p.StartMonth],100*p.EndYear+midx[p.EndMonth]]),
          d0=Math.min(...proj.map(([,p])=>p)), d1= Math.max(...proj.map(([,,p])=>p)),
          res = [["Year","Start","End","Owner"],["Month","","",""]].concat(dataArray.map(p=>[p.Project,p.StartMonth+"-"+p.StartYear,p.EndMonth+"-"+p.EndYear,p.Owner]));
    
    for (let y=Math.floor(d0/100), m=d0 % 100; (d=100*y+m)<=d1; ++m) {
     if (m==12) {m=0;y++;d=100*y+m}
     res[0].push(y);res[1].push(months[m]); // table header row
     proj.forEach(([nam,a,b],i)=>res[i+2].push( d>=a && d<=b ? "YES" :"")) // table body rows
    }
    // Append the table to the div with id 'table-container'
    
    const rep = res.shift().reduce((a,c,i)=>
      a.set(c,(a.get(c)||0)+1) // get the counts of each value c
    ,new Map());
    
    document.getElementById('table-container').innerHTML = 
     "<thead><tr>"+[...rep.entries()].map(([t,n])=>`<th${n-1?` colspan="${n}"`:""}>${t}</th>`).join("")+"</tr></thead><tbody>"
     +res.map(r=>"<tr><td>"+r.join("</td><td>")+"</td></tr>").join("n")+"</tbody>";
    table {border-collapse:collapse}
    td,th {border: 1px solid grey; padding:4px}
    tr:nth-child(even) {background-color: #eee}
    <table id="table-container"></table>
    
    <script>
    var dataArray = [
        {'Project': 'Project 1', 'StartYear': '2023', 'EndYear': '2023', 'StartMonth': 'Nov', 'EndMonth': 'Dec', 'Owner':'Tom'},
        {'Project': 'Project 1.1', 'StartYear': '2023', 'EndYear': '2023', 'StartMonth': 'Nov', 'EndMonth': 'Dec', 'Owner':'Tom'},
        {'Project': 'Project 2', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Jan', 'EndMonth': 'May', 'Owner':'Tom'},
        {'Project': 'Project 2.1', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Jan', 'EndMonth': 'Feb', 'Owner':'Tom'},
        {'Project': 'Project 2.2', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Feb', 'EndMonth': 'Mar', 'Owner':'Tom'},
        {'Project': 'Project 2.3', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Mar', 'EndMonth': 'May', 'Owner':'Tom'}
         ];
    
    </script>

    In order to have an easier structure to scan through I converted your dataArray into an array of arrays: proj. The subarrays of proj each contain three elements: the project name, a start date and an end date in numerical form (years*100 + month-index).

    In order to make the months sortable I also translated them back into numerical forms and I keep the array months and midx for lookup purposes in both directions. d0 and d1 are the start and the end of the project tables overview (moin and max dates to be displayed).

    In the main for loop I go from d0 to d1 and calculate the current year and month for each of the numerical dates. Whenever a new year is started I need to recalculate m,y and d (see after if (m==12))

    The rest of the loop is now concerned with storing a YES or a blank into the appropriate field of the 2D array res.

    Update

    I added the colspan mechanism for the table header. This involves the use of a Map object. Now the result should look exactly like the expected one.

    I am sure my answer can be simplified further. Go ahead and try it 🤷 …

    Login or Signup to reply.
  2. Tried doing it my way and it’s a bit longer than the accepted answer but I think a nicer solution in my opinion.

    Since your example table, never had a project that lasted more than one year or at least sometime in both years I’ve considered it might happen.

    The first object of dataArray is changed so it lasts three years so you can see the effect if the project lasts longer.

    Quick explanation:

    • I sorted months using an object of them with assigned number as values.
    • Extracted all years and all months available for those years into yearsAndMonthSpan variable
    • Created formattedDataArray variable where the original object was formatted with a new property with its own yearAndMonths in which the project is worked in
    • withHtml variable is getting the project objects ready for making HTML code
    • Created HTML variables and included them in the table variable for better readability
      • headersHtml
      • subheadersHtml
      • projectsHtml

    Hope it helps 🙌🏼

    Check the snippet:

    const dataArray = [{
        Project: 'Project 1',
        StartYear: '2022',
        EndYear: '2024',
        StartMonth: 'Nov',
        EndMonth: 'Mar',
        Owner: 'Tom'
      },
      {
        Project: 'Project 1.1',
        StartYear: '2022',
        EndYear: '2022',
        StartMonth: 'Mar',
        EndMonth: 'May',
        Owner: 'Tom'
      },
      {
        Project: 'Project 2',
        StartYear: '2024',
        EndYear: '2024',
        StartMonth: 'Jan',
        EndMonth: 'May',
        Owner: 'Tom'
      },
      {
        Project: 'Project 2.1',
        StartYear: '2024',
        EndYear: '2024',
        StartMonth: 'Jan',
        EndMonth: 'Feb',
        Owner: 'Tom'
      },
      {
        Project: 'Project 2.2',
        StartYear: '2024',
        EndYear: '2024',
        StartMonth: 'Feb',
        EndMonth: 'Mar',
        Owner: 'Tom'
      },
      {
        Project: 'Project 2.3',
        StartYear: '2024',
        EndYear: '2024',
        StartMonth: 'Mar',
        EndMonth: 'May',
        Owner: 'Tom'
      },
    ]
    const monthOrder = {
      Jan: 0,
      Feb: 1,
      Mar: 2,
      Apr: 3,
      May: 4,
      Jun: 5,
      Jul: 6,
      Aug: 7,
      Sep: 8,
      Oct: 9,
      Nov: 10,
      Dec: 11,
    }
    
    // Extract unique years and months
    const years = [...new Set(dataArray.map((item) => [item.StartYear, item.EndYear]).flat())].sort()
    // Sort years
    years.sort()
    
    // All years with all months from all projects.
    let yearsAndMonthSpan = {}
    
    // Loop through years of project and get months for each year.
    // Added maxMonthSpan property just for the sake of it
    const formattedDataArray = dataArray.map((item) => {
      const {
        StartYear,
        EndYear,
        StartMonth,
        EndMonth
      } = item
    
      const months = Object.keys(monthOrder)
      const startYear = Number(StartYear)
      const endYear = Number(EndYear)
    
      let loopYear = startYear
      let startMonthIndex = monthOrder[StartMonth]
      let yearAndMonths = {}
    
      while (loopYear <= endYear) {
        const endLoopNumber = loopYear === endYear ? monthOrder[EndMonth] + 1 : months.length
        let tempMonths = []
    
        for (let i = startMonthIndex; i < endLoopNumber; i++) {
          tempMonths = [...tempMonths, months[i]]
        }
    
        yearAndMonths[loopYear] = tempMonths
    
        if (!yearsAndMonthSpan[loopYear]) yearsAndMonthSpan[loopYear] = [tempMonths]
        yearsAndMonthSpan[loopYear] = [...new Set([...yearsAndMonthSpan[loopYear], tempMonths].flat())].sort((a, b) => {
          return monthOrder[a] - monthOrder[b]
        })
    
        loopYear++
        startMonthIndex = 0
      }
    
      return {
        ...item,
        years: yearAndMonths,
        maxMonthSpan: Object.values(yearAndMonths).flat().filter(Boolean).length,
      }
    })
    
    // Added HTML for each month with either YES or empty string
    // Formatted the returned project object for usage in making HTML table code easier
    const withHtml = formattedDataArray.map((item) => {
      let markedMonths = []
    
      for (const year in yearsAndMonthSpan) {
        markedMonths = [
          ...markedMonths,
          yearsAndMonthSpan[year].map((month, iMap) => {
            if (!item.years[year]) return '<td></td>'
            return item.years[year].includes(month) ? '<td>YES</td>' : '<td></td>'
          }),
        ]
      }
    
      return {
        owner: item.Owner,
        start: `${item.StartMonth}-${item.StartYear}`,
        end: `${item.EndMonth}-${item.EndYear}`,
        projectName: item.Project,
        getHtml: markedMonths,
      }
    })
    
    const headersStaticTitles = ['Year', 'Start', 'End', 'Owner']
    const yearHeaders = Object.entries(yearsAndMonthSpan)
    const monthHeaders = Object.values(yearsAndMonthSpan).flat()
    
    // Headers HTML
    const headersHtml = `<tr>
          ${headersStaticTitles.map((title) => `<th>${title}</th>`)}
          ${yearHeaders.map((item) => `<th colspan="${item[1].length}">${item[0]}</th>`)}
        </tr>`
    
    // Subheaders HTML
    const subheadersHtml = `<tr>
          <td>Month</td>
          ${Array(3)
            .fill()
            .map((x) => `<td></td>`)}
          ${monthHeaders.map((item) => `<th>${item}</th>`)}
        </tr>`
    
    // Projects HTML
    const projectsHtml = withHtml.map((item) => {
      return `<tr>
            <td>${item.projectName}</td>
            <td>${item.start}</td>
            <td>${item.end}</td>
            <td>${item.owner}</td>
            ${item.getHtml}
          </tr>`
    })
    
    // Removed commas from arrays since it showed them above the table.
    const arrayToString = (arr) => arr.toString().replaceAll(',', 'n')
    
    // Improved table
    const table = `
            <table>
              ${arrayToString(headersHtml)}
    
              ${arrayToString(subheadersHtml)}
    
              ${arrayToString(projectsHtml)}
            </table>
          `
    document.getElementById('table-container').innerHTML = table
    table {border-collapse:collapse}
    td,th {border: 1px solid grey; padding:4px}
    th {min-width: 100px;}
    tr:nth-child(even) {background-color: #eee}
    <!doctype html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Document</title>
    </head>
    
    <body>
      <div id="table-container"></div>
    </body>
    
    </html>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search