Search

Events

Time Bar

Explore mouse, keyboard and touch events by interacting with time bar.

Events
View live example →

Interact with the time bar to see events logged in the controls below it.

You can control which events are logged with the checkboxes below the time bar, and switch between time bar modes to see the differences in the returned properties.

High-precision events (onPointerMove, onWheel) fire continuously, and will usually require throttling or debouncing if you cause state changes in their handlers.

ReGraph returns detailed information on each event.

See also

import React, { useMemo, useState } from "react";
import { createRoot } from "react-dom/client";

import mapValues from "lodash/mapValues";
import throttle from "lodash/throttle";
import ReactJson from "react-json-view";
import { TimeBar } from "regraph";

import data from "./data";
import { style } from "@ci/theme/rg/js/storyStyles";

import "@ci/theme/rg/css/layout.css";
import "@ci/theme/rg/css/button.css";

export const Demo = () => <TimeBarEvents items={data} selection={          } />;

const CheckBox = ({ id, text, onChange, checked }) => (
  <label htmlFor={id} className="container">
    {text}
    <input type="checkbox" id={id} defaultChecked={checked} onChange={onChange} />
    <span className="checkmark" />
  </label>
);

const createModeButton = (modeLabel, props) => {
  const { onModeChange, mode: currentMode } = props;
  const mode = modeLabel.toLowerCase();
  const id = `${mode}Mode`;
  return (
    <label key={mode} htmlFor={id} className="legend-radio">
      {modeLabel}
      <input
        type="radio"
        id={id}
        name="mode"
        defaultChecked={currentMode === mode}
        onChange={() => onModeChange(mode)}
      />
      <span />
    </label>
  );
};

const Options = (props) => {
  const {
    onClearLogs,
    logHoverEvents,
    onLogHoverClick,
    logPrecisionEvents,
    onLogPrecisionClick,
    expandArguments,
    onExpandArgumentsClick,
  } = props;
  return (
    <div className="event-options">
      <span className="mode-label">Mode:</span>
      {createModeButton("Normal", props)}
      {createModeButton("Stacked", props)}
      {createModeButton("Smooth", props)}
      <span className="mode-label">Options:</span>
      <CheckBox
        id="logOnChartHover"
        text="Log hover events"
        onChange={onLogHoverClick}
        checked={logHoverEvents}
      />
      <CheckBox
        id="logPrecisionEvents"
        text="Log high precision events"
        onChange={onLogPrecisionClick}
        checked={logPrecisionEvents}
      />
      <CheckBox
        id="expandArguments"
        text="Expand event arguments"
        onChange={onExpandArgumentsClick}
        checked={expandArguments}
      />
      <button type="button" onClick={onClearLogs}>
        Clear logs
      </button>
    </div>
  );
};

const EventNotificationCircle = (timestamp) => {
  const now = new Date().valueOf();
  const fadeDelay = 500;
  return (
    <div
      className={
        now - timestamp > fadeDelay
          ? "event-circle old-event-circle"
          : "event-circle new-event-circle"
      }
    />
  );
};

const EventLog = (props) => {
  const { history, expandArguments, ...rest } = props;

  return (
    <div className="logs" style={{ flexBasis: 0 }} {...rest}>
      <table className="events">
        <thead>
          <tr>
            <th className="right">Recent</th>
            <th>Event</th>
            <th>Arguments</th>
          </tr>
        </thead>
               
          {history.map(({ event, value, timestamp }) => (
            <tr key={timestamp}>
              <td>{                                  }</td>
              <td>{event}</td>
              <td>
                <ReactJson
                  src={value}
                  collapsed={expandArguments ? 2 : 0}
                  name={null}
                  enableClipboard={false}
                  displayDataTypes={false}
                />
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

function formatEvent(event) {
  if (event) {
    return mapValues(event, (propValue) => {
      if (typeof propValue === "function") {
        return "function";
      }
      return propValue;
    });
  }
  return event;
}

function TimeBarEvents(props) {
  const [history, setHistory] = useState([]);
  const [demoOptions, setDemoOptions] = useState({
    logOnHover: false,
    logPrecision: false,
    expandArguments: false,
    options: {
      style: {
        ...style.primary1,
        hoverColor: style.primary0.color,
      },
    },
    mode: "normal", // 'normal' | 'stacked' | 'smooth'
  });

  const { items, selection } = props;

  const updateHistory = (eventName, event) => {
    // update the history
    setHistory((current) => {
      return [
        {
          event: eventName,
          value: formatEvent(event),
          timestamp: new Date().valueOf(),
        },
        ...current,
      ];
    });
  };

  const clearHistory = () => {
    setHistory([]);
  };

  const handleChange = (event) => {
    updateHistory("onChange", event);
  };

  const handleClick = (event) => {
    updateHistory("onClick", event);
  };

  const handleContextMenu = (event) => {
    updateHistory("onContextMenu", event);
  };

  const handleDoubleClick = (event) => {
    updateHistory("onDoubleClick", event);
  };

  const handleDragEnd = (event) => {
    updateHistory("onDragEnd", event);
  };

  const handleDragStart = (event) => {
    updateHistory("onDragStart", event);
  };

  const handleHover = (event) => {
    if (demoOptions.logOnHover) {
      updateHistory("onHover", event);
    }
  };

  const handlePointerDown = (event) => {
    updateHistory("onPointerDown", event);
  };

  const handlePointerMove = useMemo(
    () =>
      throttle((event) => {
        if (demoOptions.logPrecision) {
          updateHistory("onPointerMove", event);
        }
      }, 200),
    [demoOptions]
  );

  const handlePointerUp = (event) => {
    updateHistory("onPointerUp", event);
  };

  const handleWheel = useMemo(
    () =>
      throttle((event) => {
        if (demoOptions.logPrecision) {
          updateHistory("onWheel", event);
        }
      }, 200),
    [demoOptions]
  );

  const renderOptions = () => {
    const { expandArguments, logOnHover, logPrecision, mode } = demoOptions;
    return (
      <Options
        mode={mode}
        onClearLogs={clearHistory}
        onLogHoverClick={(event) =>
          setDemoOptions((current) => ({ ...current, logOnHover: event.target.checked }))
        }
        onLogPrecisionClick={(event) =>
          setDemoOptions((current) => ({ ...current, logPrecision: event.target.checked }))
        }
        onExpandArgumentsClick={(event) =>
          setDemoOptions((current) => ({ ...current, expandArguments: event.target.checked }))
        }
        onModeChange={(newMode) => {
          const options = {};
          if (newMode === "stacked") {
            options.stack = {
              by: "id",
              styles: {
                x: { ...style.primary3, hoverColor: style.primary2.color },
                y: { ...style.primary2, hoverColor: style.primary1.color },
              },
            };
          }
          setDemoOptions((current) => ({ ...current, mode: newMode, options }));
        }}
        expandArguments={expandArguments}
        logHoverEvents={logOnHover}
        logPrecisionEvents={logPrecision}
      />
    );
  };

  const { expandArguments, mode, options } = demoOptions;
  return (
    <div className="story">
      <div className="chart-wrapper">
        <TimeBar
          items={items}
          mode={mode === "smooth" ? "smooth" : "histogram"}
          onChange={handleChange}
          onClick={handleClick}
          onContextMenu={handleContextMenu}
          onDoubleClick={handleDoubleClick}
          onDragEnd={handleDragEnd}
          onDragStart={handleDragStart}
          onHover={handleHover}
          onPointerDown={handlePointerDown}
          onPointerMove={handlePointerMove}
          onPointerUp={handlePointerUp}
          onWheel={handleWheel}
          options={options}
          selection={selection}
          selectionColor={style.secondary1.color}
          style={{ flex: 3 }}
        />
      </div>
      <div className="event-panel">
        {               }
        <EventLog history={history} expandArguments={expandArguments} />
      </div>
    </div>
  );
}

function selected() {
  return { id1: true };
}

const root = createRoot(document.getElementById("regraph"));
root.render(<Demo />);
const data = {
  id1: { times: [{ time: Date.now(), value: 2 }], data: { id: "x" } },
  id2: { times: [{ time: Date.now() + 1000 * 60 * 60 }], data: { id: "x" } },
  id3: { times: [{ time: Date.now() + 1000 * 60 * 60 * 3, value: 3 }], data: { id: "x" } },
  id4: { times: [{ time: Date.now(), value: 2.5 }], data: { id: "y" } },
  id5: { times: [{ time: Date.now() + 1000 * 60 * 60 }], data: { id: "y" } },
  id6: { times: [{ time: Date.now() + 1000 * 60 * 60 * 3, value: 2 }], data: { id: "y" } },
  id7: { times: [{ time: Date.now() + 1000 * 60 * 60 * 3, value: 1.5 }], data: { id: "z" } },
};

export default data;
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div id="regraph" style="height: 100vh"></div>
    <script type="module" src="./code.jsx"></script>
  </body>
</html>
@import "@ci/theme/rg/css/variables.css";

table.events {
  min-width: 430px;
}

table.events td {
  vertical-align: text-top;
  padding: var(--light-mode-button-padding);
  font-family: monospace;
}

table.events th {
  text-align: left;
  padding: var(--light-mode-button-padding);
}

table.events th.right {
  text-align: right;
}

.event-circle {
  transition: background-color 1200ms ease-out;
  -webkit-transition: background-color 1200ms ease-out;
  height: 6px;
  width: 6px;
  border-radius: 50%;
  float: right;
  background-color: white;
}

.new-event-circle {
  background-color: #2dcda8;
}

.old-event-circle {
  background-color: white;
}

.checkbox {
  padding-left: 12px;
}

.checkbox-container-fixed {
  position: absolute;
  top: -16px;
  background-color: rgba(255, 255, 255, 0.4);
}

.log-container {
  display: flex;
  padding: 36px 12px 12px 12px;
  height: 70%;
}

.logs {
  font-family: monospace;
  border: solid 1px lightgray;
  overflow: auto;
  margin: 0;
  flex: 1 1 auto;
}

.logs .react-json-view {
  white-space: nowrap;
  /* Prevents jump when opening/closing events */
  min-width: 316px;
}

.logs .react-json-view .string-value {
  white-space: initial;
}

.event-panel .logs {
  border: none;
}

.event-panel .logs .count > * {
  background-color: var(--secondary1);
  color: white;
  border-radius: 20px;
  padding: 4px 8px;
}

.log-container-fixed {
  display: flex;
  padding: 36px 12px 12px 12px;
  position: absolute;

  bottom: 0;
  height: 50vh;
  z-index: 400;
}

/* Customize the label (the container) */
.container {
  display: block;
  position: relative;
  padding-left: 35px;
  margin: 6px 12px 6px 24px;
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

/* Hide the browser's default checkbox */
.container input {
  position: absolute;
  opacity: 0;
  cursor: pointer;
  height: 0;
  width: 0;
}

.mode-label {
  margin-left: 12px;
}

/* Create a custom checkbox */
.checkmark {
  position: absolute;
  top: -3px;
  left: 0;
  height: 25px;
  width: 25px;
  background-color: #eee;
}

/* On mouse-over, add a grey background color */
.container:hover input ~ .checkmark {
  background-color: #ccc;
}

/* When the checkbox is checked, add a teal background */
.container input:checked ~ .checkmark {
  background-color: var(--primary2);
}

/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
  content: "";
  position: absolute;
  display: none;
}

.checkmark.round {
  border-radius: var(--light-mode-small-border-radius);
}

/* Show the checkmark when checked */
.container input:checked ~ .checkmark:after {
  display: block;
}

input[disabled] {
  opacity: 0.4;
}

/* Style the checkmark/indicator */
.container .checkmark:after {
  left: 9px;
  top: 5px;
  width: 5px;
  height: 10px;
  border: solid white;
  border-width: 0 3px 3px 0;
  -webkit-transform: rotate(45deg);
  -ms-transform: rotate(45deg);
  transform: rotate(45deg);
}

.event-options {
  flex: 1;
  flex-direction: column;
  max-width: 300px;
  align-items: flex-start;
}

.event-options .legend-radio {
  margin-left: 22px;
}

.event-options .legend-radio:hover {
  background-color: inherit;
}

.event-options .checkmark {
  border: solid 2px var(--primary2);
  background-color: white;
  width: 21px;
  height: 21px;
}

.event-options .container:hover input ~ .checkmark {
  background-color: #eee;
}

.event-options .container:hover input:checked ~ .checkmark {
  background-color: var(--primary1);
}

.event-options .checkmark:after {
  left: 7px;
  top: 3px;
}

#clear-logs {
  float: right;
}

.chart-wrapper {
  flex-grow: 5;
}
.event-panel {
  display: flex;
  flex: 1;
  flex-grow: 3;
  border-top: solid 1px #c0c0c0;
  background-color: white;
  padding-top: 8px;
  max-height: 300px;
  overflow: auto;
}

/*  Webkit hides scroll bars. Force them on the log container so that
    You get a sense of more logs being added
 */
.logs::-webkit-scrollbar {
  -webkit-appearance: none;
}

.logs::-webkit-scrollbar:vertical {
  width: 11px;
}

.logs::-webkit-scrollbar:horizontal {
  height: 11px;
}

.logs::-webkit-scrollbar-thumb {
  border-radius: 8px;
  border: 2px solid white; /* should match background, can't be transparent */
  background-color: rgba(0, 0, 0, 0.5);
}

.logs::-webkit-scrollbar-track {
  background-color: #fff;
  border-radius: 8px;
}

.options input {
  box-shadow: none;
}

.options input:hover {
  box-shadow: none;
}

Terms of use

These terms do not alter or supersede any existing agreements between you (or your employer) and us.

By accessing or using any Content you agree to be bound by these Terms of Use. Please review these terms carefully before using the website.

The contents of this website, including but not limited to any text, code samples, API references, schemas, interactive tools, and other materials (collectively, the 'Content'), are made available for informational and internal evaluation purposes only. All intellectual property rights in the Content are reserved. No licence is granted to use the Content for any commercial purpose, or to copy, distribute, modify, reverse-engineer, or incorporate any part of the Content into any product or service, without our prior written consent.

This Content is provided “as is” and “as available,” without any representations, warranties, or guarantees of any kind, whether express or implied, including but not limited to implied warranties of merchantability, fitness for a particular purpose, non-infringement, or accuracy. To the fullest extent permitted by applicable law, we expressly exclude and disclaim all implied warranties, conditions, and other terms that might otherwise be implied.

We disclaim all liability for any loss or damage, whether direct, indirect, incidental, consequential, or otherwise, arising from any reliance placed on the Content or from your use of it, to the fullest extent permitted by applicable law. By continuing to access or use the Content, you acknowledge and agree to these terms.