bryntum / support

An issues-only repository for the Bryntum project management component suite which includes powerful Grid, Scheduler, Calendar, Kanban Task Board and Gantt chart components all built in pure JS / CSS / TypeScript
https://www.bryntum.com
53 stars 6 forks source link

Improve performance of region resize #7372

Closed taauntik closed 8 months ago

taauntik commented 1 year ago

Forum post

It is based on https://bryntum.com/products/scheduler/examples/bigdataset/

Resources: 1000 Events: 100

replace the whole code in demo with this

import { DateHelper, PresetManager, AsyncHelper, WidgetHelper, DataGenerator, Scheduler } from '../../build/scheduler.module.js?470047';
import shared from '../_shared/shared.module.js?470047';

PresetManager.removeAll();

PresetManager.registerPreset(`preset-level-0`, {
  displayDateFormat: "HH:mm", // Controls how dates will be displayed in tooltips etc

  shiftIncrement: 1, // Controls how much time to skip when calling shiftNext and shiftPrevious.
  shiftUnit: "hour", // Valid values are 'millisecond', 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'.
  defaultSpan: 24, // By default, if no end date is supplied to a view it will show 12 hours

  timeResolution: {
    // Dates will be snapped to this resolution
    unit: "minute", // Valid values are 'millisecond', 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'.
    increment: 1,
  },

  headers: [
    {
      unit: "year",
      align: "start",
      headerCellCls: "ww-timeaxis-hour",
      renderer: function (start, _end, headerConfig, _index) {
        return start.getHours();
      },
    },
  ],

  id: `preset-level-0`,
  name: `preset-level-0`,
  tickWidth: 25,
});

async function generateResources(
  resourceCount = resourceCountField.value,
  eventCount = eventCountField.value
) {
  const today = DateHelper.clearTime(new Date()),
    mask = WidgetHelper.mask(scheduler.element, "Generating records"),
    colors = ["cyan", "green", "indigo"],
    resources = [],
    events = [],
    assignments = [],
    dependencies = [],
    resourceTimeRanges = [],
    timeRanges = [],
    useResourceTimeRanges = !scheduler.features.resourceTimeRanges.disabled,
    useTimeRanges = !scheduler.features.timeRanges.disabled,
    useDependencies = !scheduler.features.dependencies.disabled;

  let schedulerEndDate = today,
    i,
    step;

  console.time("generate");

  const generator = DataGenerator.generate(resourceCount);

  while ((step = generator.next()) && !step.done) {
    const res = step.value;

resources.push(res);

for (i = 0; i < eventCount; i++) {
  const startDate = DateHelper.add(
      today,
      Math.round(Math.random() * (i + 1) * 20),
      "days"
    ),
    duration = Math.round(Math.random() * 9) + 2,
    endDate = DateHelper.add(startDate, duration, "days"),
    eventId = events.length + 1;
  console.log(startDate, endDate);
  events.push({
    id: eventId,
    name: "Task #" + (events.length + 1),
    startDate,
    duration,
    endDate,
    eventColor: colors[resources.length % 3],
  });

  assignments.push({
    id: "a" + eventId,
    event: eventId,
    resource: res.id,
  });

  if (useDependencies && i > 0) {
    dependencies.push({
      id: dependencies.length + 1,
      from: eventId - 1,
      to: eventId,
    });
  }

  if (useResourceTimeRanges && i % 2 === 0) {
    resourceTimeRanges.push({
      id: resourceTimeRanges.length + 1,
      resourceId: res.id,
      name: `Range ${resourceTimeRanges.length + 1}`,
      startDate: DateHelper.add(
        startDate,
        Math.round(Math.random() * 5),
        "days"
      ),
      duration: Math.round(Math.random() * 5) + 4,
      timeRangeColor: "red",
    });
  }

  if (endDate > schedulerEndDate) {
    schedulerEndDate = endDate;
  }
}

if (resources.length % 2000 === 0) {
  mask.text = `Generated ${resources.length * eventCount} of ${
    resourceCount * eventCount
  } records`;

  await AsyncHelper.animationFrame();
}
  }

  if (useTimeRanges) {
    let startDate = today;
    for (let i = 0; i < eventCount; i++) {
      startDate = DateHelper.add(
        startDate,
        Math.round(Math.random() * 8) + 15,
        "days"
      );

  timeRanges.push({
    id: i + 1,
    name: `Time range ${i + 1}`,
    startDate,
    duration: Math.round(Math.random() * 5) + 4,
  });
}
  }

  console.timeEnd("generate");

  mask.text = "Loading data";

  // Give the UI a chance to catch up, to update the mask before proceeding
  await AsyncHelper.sleep(100);

  console.time("data");

  scheduler.suspendRefresh();
  scheduler.endDate = schedulerEndDate;
  scheduler.project = {
    assignmentStore: {
      // Boost record creation performance a bit, by not cloning the raw data object
      useRawData: true,
      data: assignments,
    },
    resourceStore: {
      useRawData: true,
      data: resources,
    },
    eventStore: {
      useRawData: true,
      data: events,
    },
    dependencyStore: {
      useRawData: true,
      data: dependencies,
    },
    resourceTimeRangeStore: {
      useRawData: true,
      data: resourceTimeRanges,
    },
    timeRangeStore: {
      useRawData: true,
      data: timeRanges,
    },
  };
  scheduler.resumeRefresh(true);

  await scheduler.project.await("refresh");

  console.timeEnd("data");

  mask.close();
}

function toggleCustom(show) {
  scheduler.widgetMap.featuresButton.hidden =
    resourceCountField.hidden =
    eventCountField.hidden =
      !show;
}

function applyPreset(resources, events) {
  toggleCustom(false);

  resourceCountField.value = resources;
  eventCountField.value = events;

  generateResources();
}

const scheduler = new Scheduler({
  viewPreset: "preset-level-0",
  appendTo: "container",
  eventStyle: "border",
  rowHeight: 50,
  startDate: DateHelper.clearTime(new Date()),
  // Turn off animations, to speed up the demo a bit since a lot will be on screen
  useInitialAnimation: false,
  enableEventAnimations: false,
  // Don't track timeline context while scrolling, speeds up scrolling a bit by hitting DOM less
  updateTimelineContextOnScroll: false,
  // Disabling sticky headers speeds up scrolling a bit, since `position: sticky` promotes element to its own layer
  stickyHeaders: false,

  generateResources,

  columns: [
    { type: "rownumber" },
    { text: "Id", field: "id", width: 50, hidden: true },
    { text: "First name", field: "firstName", flex: 1 },
    { text: "Surname", field: "surName", flex: 1 },
    { text: "Score", field: "score", type: "number", flex: 1 },
  ],

  features: {
    // Features that can be toggled in Custom mode
    dependencies: {
      disabled: true,
    },
    resourceTimeRanges: {
      disabled: true,
    },
    timeRanges: {
      disabled: true,
      enableResizing: true,
    },
    eventNonWorkingTime: {
      disabled: true,
    },
    nonWorkingTime: {
      disabled: true,
    },
    // Turn off schedule tooltip to boost scrolling performance a bit
    scheduleTooltip: false,
    // Disabling sticky events speeds up scrolling a bit, since `position: sticky` promotes element to its own layer
    stickyEvents: false,
  },

  tbar: [
    "Presets",
    {
      type: "buttongroup",
      toggleGroup: true,
      items: [
        {
          text: "1K events",
          ref: "1K",
          pressed: true,
          dataConfig: {
            resources: 200,
            events: 5,
          },
        },
        {
          text: "5K events",
          ref: "5K",
          dataConfig: {
            resources: 1000,
            events: 5,
          },
        },
        {
          text: "10K events",
          ref: "10K",
          dataConfig: {
            resources: 1000,
            events: 10,
          },
        },
        {
          text: "Custom",
          ref: "customButton",
          onClick() {
            toggleCustom(true);
          },
        },
      ],
      onClick({ source: button }) {
        if (button.dataConfig) {
          applyPreset(button.dataConfig.resources, button.dataConfig.events);
        }
      },
    },
    "->",
    {
      ref: "resourceCountField",
      type: "number",
      placeholder: "Number of resources",
      label: "Resources",
      tooltip: "Enter number of resource rows to generate and press [ENTER]",
      min: 1,
      max: 10000,
      width: "auto",
      inputWidth: "5em",
      keyStrokeChangeDelay: 500,
      changeOnSpin: 500,
      hidden: true,
      onChange: ({ userAction }) => userAction && generateResources(),
    },
    {
      ref: "eventCountField",
      type: "number",
      placeholder: "Number of events",
      label: "Events",
      tooltip:
        "Enter number of events per resource to generate and press [ENTER]",
      min: 1,
      max: 100,
      width: "auto",
      inputWidth: "4em",
      keyStrokeChangeDelay: 500,
      changeOnSpin: 500,
      hidden: true,
      onChange: ({ userAction }) => userAction && generateResources(),
    },
    {
      type: "button",
      ref: "featuresButton",
      text: "Features",
      menu: [
        {
          text: "Dependencies",
          checked: false,
          onToggle: ({ checked }) => {
            scheduler.features.dependencies.disabled = !checked;

        if (checked && !scheduler.dependencyStore.count) {
          generateResources();
        }
      },
    },
    {
      text: "Resource ranges",
      checked: false,
      onToggle: ({ checked }) => {
        scheduler.features.resourceTimeRanges.disabled = !checked;

        if (checked && !scheduler.resourceTimeRangeStore.count) {
          generateResources();
        }
      },
    },
    {
      text: "Time ranges",
      checked: false,
      onToggle: ({ checked }) => {
        scheduler.features.timeRanges.disabled = !checked;

        if (checked && !scheduler.timeRangeStore.count) {
          generateResources();
        }
      },
    },
    {
      text: "Non-working time",
      checked: false,
      onToggle: ({ checked }) => {
        scheduler.features.nonWorkingTime.disabled = !checked;
      },
    },
    {
      text: "Event non-working time",
      checked: false,
      onToggle: ({ checked }) => {
        scheduler.features.eventNonWorkingTime.disabled = !checked;
      },
    },
  ],
},
  ],
});

const { resourceCountField, eventCountField } = scheduler.widgetMap;

applyPreset(200, 5);

https://github.com/bryntum/support/assets/75997756/72296cee-9ccf-47ab-a7a3-07f313e69de5

matsbryntse commented 9 months ago

progress pushed to 7372-regionresize