Search

Font Icons

Integrations > KeyLines and ReGraph

Use Font Awesome icons in both the timeline and graph.

Font Icons
View live example →

Font Icons can be used in both KronoGraph and KeyLines.

This story uses Font Awesome icons on nodes and links in the KeyLines chart, and on entities and events in the KronoGraph timeline.

See also:

Font Icons can be used in both KronoGraph and ReGraph.

This story uses Font Awesome icons on nodes and links in the ReGraph chart, and on entities and events in the KronoGraph timeline.

See also:

import { createTimeline } from "kronograph";
import KeyLines from "keylines";
import { isEmpty } from "lodash";
import { chartData, timelineData, timelineOptions, chartOptions } from "./data";

async function loadFonts() {
  document.fonts.load('900 16px "Font Awesome 5 Free"');
  document.fonts.load('500 16px "Font Awesome 5 Brands"');
  await document.fonts.ready;
}

async function main() {
  async function applySelection(selection) {
    chart.selection(selection);
    const selectedNodeIds = selection.filter((itemId) => chart.getItem(itemId).type === "node");
    timeline.focus(selectedNodeIds);

    const neighborsOfSelection = await chart.graph().neighbours(selection);
    if (isEmpty(selection)) {
      // If nothing is selected, nothing is faded.
      chart.foreground(() => true);
    } else {
      // Fade nodes that are neither themselves selected nor neighboring a selected node.
      chart.foreground(
        (node) => selection.includes(node.id) || neighborsOfSelection.nodes.includes(node.id)
      );
    }
  }

  async function timelineRangeHandler() {
    // When timeline range changes, filter the items in the KeyLines chart.
    const { entities: visibleEntityIds } = timeline.getInRangeItems({ focus: false });
    const filterOptions = { type: "node", animate: false };
    const { shown, hidden } = await chart.filter(
      (item) => visibleEntityIds[item.id],
      filterOptions
    );
    if (!isEmpty(shown.nodes) || !isEmpty(hidden.nodes)) {
      await chart.layout("organic");
    }
  }

  await loadFonts();

  const timeline = createTimeline({ container: "timeline", options: timelineOptions });
  const chart = await KeyLines.create({ container: "chart", options: chartOptions });

  // When the focus on the timeline changes, update the KeyLines chart.
  timeline.on("focus", ({ focus }) => applySelection(focus));
  timeline.on("range", timelineRangeHandler);
  chart.on("selection-change", () => applySelection(chart.selection()));

  // prevent selection of links
  chart.on("click", (ev) => {
    if (ev.id) {
      const item = chart.getItem(ev.id);
      if (item.type === "link") {
        ev.preventDefault();
      }
    }
  });

  timeline.set(timelineData);
  timeline.fit();

  await chart.load(chartData);
  await chart.layout();
  await chart.zoom("fit");
}

main();
import keyBy from "lodash/keyBy";
import mapValues from "lodash/mapValues";

const colorPalette = {
  lightBlueGray: "#e5e8eb",
  darkBlueGray: "#242c32",
  darkBlueGrayAlpha: "rgba(36, 44, 50, 0.8)",
  blueGray: "#606d7b",
  orange: "#f58a3d",
  purple: "#7069FA",
  red: "#ef4e4e",
  blue: "#4098D7",
  yellow: "#f7d06e",
  transparent: "rgba(0, 0, 0, 0)",
};

export const timelineOptions = {
  entities: { glyphPosition: "right" },
  focus: {
    backgroundColor: colorPalette.blueGray,
  },
  scales: { showAtBottom: false },
};

export const chartOptions = {
  backgroundAlpha: 0.3,
  backColour: colorPalette.darkBlueGray,
  selectionColour: colorPalette.blueGray,
  navigation: { shown: false },
  controlTheme: "dark",
  handMode: true,
  minZoom: 0.2,
  overview: false,
  iconFontFamily: "Font Awesome 5 Free",
  imageAlignment: {
    "\uf39f": { e: 1.4 },
    "\uf3d0": { e: 1.4 },
    "\uf42e": { e: 1.4 },
    "\uf15a": { e: 1.4 },
    "\uf232": { e: 1.4 },
  },
};

const nodesInfo = [
  {
    id: "1",
    name: "Alexander",
    fontIcon: "fas fa-user-graduate",
    fontChar: "\uf501",
    color: colorPalette.orange,
  },
  {
    id: "2",
    name: "Bernadette",
    fontIcon: "fas fa-user-ninja",
    fontChar: "\uf504",
    color: colorPalette.purple,
  },
  {
    id: "3",
    name: "Deirdre",
    fontIcon: "fas fa-user-secret",
    fontChar: "\uf21b",
    color: colorPalette.red,
  },
  {
    id: "4",
    name: "Irene",
    fontIcon: "fas fa-user-tie",
    fontChar: "\uf508",
    color: colorPalette.blue,
  },
  {
    id: "5",
    name: "John",
    fontIcon: "fas fa-user-astronaut",
    fontChar: "\uf4fb",
    color: colorPalette.yellow,
  },
];

const linksInfo = [
  { id: "a1", id1: "1", id2: "2", timestamp: 1625518983000, icon: "\uf39f" /* facebook message */ },
  { id: "a2", id1: "1", id2: "3", timestamp: 1625571591000, icon: "\uf3d0" /* monero */ },
  { id: "a3", id1: "3", id2: "4", timestamp: 1625681838000, icon: "\uf42e" /* ethereum */ },
  { id: "a4", id1: "3", id2: "5", timestamp: 1625723792000, icon: "\uf15a" /* bitcoin */ },
  { id: "a5", id1: "3", id2: "1", timestamp: 1625773792000, icon: "\uf232" /* whatsapp message */ },
];

const nodes = nodesInfo.map(({ name, fontIcon, color }, idx) => {
  return {
    type: "node",
    id: `${idx + 1}`,
    c: color,
    t: name, // label text
    tc: false, // don't center the label
    fi: { t: fontIcon },
    fc: colorPalette.lightBlueGray, // label text color
    fbc: colorPalette.darkBlueGrayAlpha, // label text background color
  };
});

const links = linksInfo.map(({ id, id1, id2, timestamp, icon }) => {
  const { color: endColor } = nodesInfo.find((node) => node.id === id1);
  const { color: startColor } = nodesInfo.find((node) => node.id === id2);
  return {
    id,
    id1,
    id2,
    type: "link",
    c: startColor, // color at id1 end
    c2: endColor, // color at id2 end
    a2: true, // show arrow at id2 end
    w: 1.5, // width
    d: { time: new Date(timestamp) },
    g: [{ c: endColor, b: colorPalette.transparent, fi: { t: icon, ff: "Font Awesome 5 Brands" } }], // glyph with fonticon
  };
});

export const chartData = {
  type: "LinkChart",
  items: [...nodes, ...links],
};

const entities = mapValues(keyBy(nodesInfo, "id"), ({ name, fontChar, color }) => {
  return {
    color,
    label: name,
    glyph: { fontIcon: { text: fontChar } },
  };
});

const events = mapValues(keyBy(linksInfo, "id"), ({ id1, id2, timestamp, icon }) => {
  return {
    entityIds: [id1, id2],
    time: new Date(timestamp),
    fontIcon: { text: icon },
  };
});

const entityTypes = {
  default: {
    glyph: { fontIcon: { fontFamily: "Font Awesome 5 Free", fontWeight: 900, scale: 1.4 } },
  },
};

const eventTypes = { default: { fontIcon: { fontFamily: "Font Awesome 5 Brands", scale: 1.5 } } };
export const timelineData = { events, entities, entityTypes, eventTypes };
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="@ci/theme/kg/css/examples.css" />
  </head>
  <body>
    <div style="display: flex; flex-direction: column; height: 100%">
      <div id="chart" style="flex: 1"></div>
      <div id="timeline" style="flex: 1"></div>
    </div>
    <script type="module" src="./code.js"></script>
  </body>
</html>
import React, { useState, useRef, useEffect } from "react";
import { createRoot } from "react-dom/client";
import Timeline from "kronograph/react/Timeline";
import { Chart } from "regraph";
import { neighbors } from "regraph/analysis";
import { pickBy, mapValues } from "lodash";
import { chartData, timelineData, timelineOptions, chartOptions } from "./data";

function isLink(item) {
  return item.id1 && item.id2;
}

function preventLinkSelection(event) {
  const item = chartData[event.id];
  if (item && isLink(item)) {
    event.preventDefault();
  }
}

export const Demo = () => {
  const [focusedItems, setFocusedItems] = useState({});
  const [chartItems, setChartItems] = useState(chartData);
  const [fontsReady, setFontsReady] = useState(false);
  const timeline = useRef(null);

  async function loadFonts() {
    document.fonts.load('900 16px "Font Awesome 5 Free"');
    document.fonts.load('500 16px "Font Awesome 5 Brands"');
    await document.fonts.ready;
    setFontsReady(true);
  }

  useEffect(() => {
    loadFonts();
  }, []);

  async function chartInteractionHandler({ id, selected, setStyle }) {
    if (selected) {
      const neighborsOfSelected = await neighbors(chartItems, id);
      // Fade everything except for neighbors of the selected item, and the
      // selected item itself.
      setStyle(
        mapValues(chartItems, (_item, itemId) => {
          return {
            fade: !neighborsOfSelected[itemId] && itemId !== id,
          };
        })
      );
    }
  }

  function timelineChangeHandler({ range, focus }) {
    // When timeline range changes, filter the items in the ReGraph chart.
    if (range) {
      const { entities, events } = timeline.current.getInRangeItems({ focus: false });
      setChartItems(pickBy(chartData, (_item, id) => events[id] || entities[id]));
    }

    // In the ReGraph chart, select all items that are focused in the timeline.
    if (focus) {
      setFocusedItems(Object.fromEntries(focus.map((id) => [id, true])));
    }
  }

  function chartChangeHandler({ selection }) {
    // In the timeline, focus all items that are selected in the ReGraph chart.
    if (selection) {
      setFocusedItems(selection);
    }
  }

  if (!fontsReady) {
    return null;
  }

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
      <Chart
        id="chart"
        items={chartItems}
        selection={focusedItems}
        options={chartOptions}
        onChange={chartChangeHandler}
        onClick={preventLinkSelection}
        onItemInteraction={chartInteractionHandler}
      />
      <Timeline
        id="timeline"
        {...timelineData}
        ref={timeline}
        focus={Object.keys(focusedItems)}
        options={timelineOptions}
        onTimelineChange={timelineChangeHandler}
      />
    </div>
  );
};

const root = createRoot(document.getElementById("my-timeline"));
root.render(<Demo />);
import mapValues from "lodash/mapValues";

const colorPalette = {
  lightBlueGray: "#e5e8eb",
  darkBlueGray: "#242c32",
  darkBlueGrayAlpha: "rgba(36, 44, 50, 0.8)",
  blueGray: "#606d7b",
  orange: "#f58a3d",
  purple: "#7069FA",
  red: "#ef4e4e",
  blue: "#4098D7",
  yellow: "#f7d06e",
};

const nodesInfo = {
  1: {
    name: "Alexander",
    fontIcon: "fas fa-user-graduate",
    fontChar: "\uf501",
    color: colorPalette.orange,
  },
  2: {
    name: "Bernadette",
    fontIcon: "fas fa-user-ninja",
    fontChar: "\uf504",
    color: colorPalette.purple,
  },
  3: {
    name: "Deirdre",
    fontIcon: "fas fa-user-secret",
    fontChar: "\uf21b",
    color: colorPalette.red,
  },
  4: { name: "Irene", fontIcon: "fas fa-user-tie", fontChar: "\uf508", color: colorPalette.blue },
  5: {
    name: "John",
    fontIcon: "fas fa-user-astronaut",
    fontChar: "\uf4fb",
    color: colorPalette.yellow,
  },
};

const linksInfo = {
  a1: { id1: "1", id2: "2", timestamp: 1625518983000, fontIcon: "\uf39f" /* facebook message */ },
  a2: { id1: "1", id2: "3", timestamp: 1625571591000, fontIcon: "\uf3d0" /* monero */ },
  a3: { id1: "3", id2: "4", timestamp: 1625681838000, fontIcon: "\uf42e" /* ethereum */ },
  a4: { id1: "3", id2: "5", timestamp: 1625723792000, fontIcon: "\uf15a" /* bitcoin */ },
  a5: { id1: "3", id2: "1", timestamp: 1625773792000, fontIcon: "\uf232" /* whatsapp message */ },
};

const nodes = mapValues(nodesInfo, ({ name, fontIcon, color }) => {
  return {
    label: {
      text: name,
      center: false,
      backgroundColor: colorPalette.darkBlueGrayAlpha,
      color: colorPalette.lightBlueGray,
    },
    fontIcon: { text: fontIcon },
    color,
  };
});

const links = mapValues(linksInfo, ({ id1, id2, timestamp, fontIcon }) => {
  const startColor = nodes[id2].color;
  const endColor = nodes[id1].color;
  return {
    id1,
    id2,
    color: startColor,
    end2: { color: endColor, arrow: true },
    data: { time: new Date(timestamp) },
    glyphs: [
      {
        fontIcon: { text: fontIcon, fontFamily: "Font Awesome 5 Brands" },
        color: startColor,
      },
    ],
    width: 1.5,
  };
});

const entityTypes = {
  default: {
    glyph: { fontIcon: { fontFamily: "Font Awesome 5 Free", fontWeight: 900, scale: 1.4 } },
  },
};

const eventTypes = { default: { fontIcon: { fontFamily: "Font Awesome 5 Brands", scale: 1.5 } } };

const entities = mapValues(nodesInfo, ({ name, fontChar, color }) => {
  return {
    label: name,
    color,
    glyph: { fontIcon: { text: fontChar } },
  };
});

const events = mapValues(linksInfo, ({ id1, id2, timestamp, fontIcon }) => {
  return {
    time: new Date(timestamp),
    entityIds: [id1, id2],
    fontIcon: { text: fontIcon },
  };
});

export const chartData = { ...nodes, ...links };

export const timelineData = { events, entities, entityTypes, eventTypes };

export const timelineOptions = {
  focus: { backgroundColor: colorPalette.blueGray },
  scales: { showAtBottom: false },
  entities: { glyphPosition: "right" },
};

export const chartOptions = {
  backgroundColor: colorPalette.darkBlueGray,
  selection: { color: colorPalette.blueGray },
  navigation: { visible: false },
  minZoom: 0.2,
  overview: false,
  controlTheme: "dark",
  iconFontFamily: "Font Awesome 5 Free",
  imageAlignment: {
    "\uf39f": { size: 1.4 },
    "\uf3d0": { size: 1.4 },
    "\uf42e": { size: 1.4 },
    "\uf15a": { size: 1.4 },
    "\uf232": { size: 1.4 },
  },
};
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="@ci/theme/kg/css/examples.css" />
  </head>
  <body>
    <div id="my-timeline" style="height: 100vh"></div>
    <script type="module" src="./code.jsx"></script>
  </body>
</html>

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.