search envelope-o feed check
Home Unanswered Active Tags New Question
user comment-o

How to do totals by column and conditional coloring of cells

Asked by Peter
1 day ago.

In my application each event has as weight of sorts (think of it as a percentage), and I want to show the totals-by-column. If the total exceeds 100% I want to color it red. I got quite far but I have some loose ends, and I worry I may have the wrong approach.

Here’s what it looks like (ignore the blue columns, that’s something else.)

What I’m trying:

  • I store the event weight in event.tags.weight

  • In various places like onEventMoved or onTimeRangeSelected I call a recomputeWeights() function

  • My recomputeWeights() function iterates through all columns. I didn’t find a direct way to iterate the columns, but I’m doing
    for (cell of rows.find(“1”).cells.all())
    where row 1 is just a representative row, and that works well enough

  • I use events.forRange(cell.start, cell.end) to find all the events in that column, and add up the weights from the tags

  • I store that in a simple weights object where the key is cell.start and the value is the weight

  • Then, in onBeforeTimeHeaderRender and onBeforeCellRender I can look up the weight by cell-start in my weights object

A few things are not yet working, or feel icky:

  • When you drag to create a new event, I call events.add(), and then recomputeWeights(), but the new event isn’t present yet (race condition) so it doesn’t update correctly

  • I haven’t found where to compute the weights on first open

  • I have to “prod” the Scheduler to repaint the headers. I’m doing:
    dp.update({

    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"},{"groupBy":"Day","format":"d"}],

    });
    Which isn’t changing the timeHeaders, just asking them to get redrawn.

Is there a better approach for any of this?

Comment posted by Peter
1 day ago.

Attaching a screenshot.

Answer posted by Dan Letecky [DayPilot]
11 hours ago.

Sorry for the delay.

I would make a few changes:

  1. Instead of using a special time header row, use a frozen row.

  2. Mark this row with cellsAutoUpdated: true, that will re-render the content on every change (events loaded, event moved or created…).

  3. Use onBeforeCellRender to calculate and display the column total (there is no need to pre-calculate it).

The key elements could look like this:

A. When loading resources, prepend the frozen row with the totals

async loadResources() {
  const {data} = await DayPilot.Http.get("/resources/");
  const resources = [
    {name: "Totals", id: "totals", frozen: "top", cellsAutoUpdated: true},
    ...data
  ];
  dp.update({resources});
}

B. Calculate the totals:

const scheduler = new DayPilot.Scheduler("scheduler", {
  onBeforeCellRender: args => {
    if (args.cell.resource === "totals") {
      const events = scheduler.events.forRange(args.cell.start, args.cell.end);

      const total = events.reduce((sum, e) => {
        const weight = e.data?.tags?.weight ?? 0;
        return sum + (Number.isFinite(n) ? n : 0);
      }, 0);

      args.cell.properties.text = total;
    }
  },
  // ..
});
// ...

You can also take a look at the following tutorial which implements a simplified version of this logic (it just counts the events):

Let me know if it doesn’t work as expected.

Comment posted by Dan Letecky [DayPilot]
10 hours ago.

And to color the cells, you can update the cache object (totals) when rendering the frozen row and reuse the value for the standard cells:

const totals = {};

const scheduler = new DayPilot.Scheduler("scheduler", {
  onBeforeCellRender: args => {
    if (args.cell.resource === "totals") {
      const events = scheduler.events.forRange(args.cell.start, args.cell.end);

      const total = events.reduce((sum, e) => {
        const weight = e.data?.tags?.weight ?? 0;
        return sum + (Number.isFinite(n) ? n : 0);
      }, 0);

      args.cell.properties.text = total;
      
      if (total > 100) {
        args.cell.properties.backColor = "#cc0000";
      }
      
      totals[args.cell.start.toString()] = total;
    }
    else {
      if (totals[args.cell.start.toString()] > 100) {
        args.cell.properties.backColor = "#ee0000";
      }
    }
  },
  // ..
});

Cells are always rendered from top to bottom. The frozen row will be updated on every change (cellsAutoUpdated). That ensures the cached values will be up-to-date when rendering standard cells.

Comment posted by Peter
9 hours ago.

Thanks, I’ll definitely try this. Since my totals are effectively a column property, I was trying too hard to compute it per column, but obviously per-cell will be fast enough, which removes a chunk of my convolutions.

New Reply
This reply is
Attachments:
or drop files here
Your name (optional):