Skip to content

Commit

Permalink
Finalized docs, minor changes
Browse files Browse the repository at this point in the history
  • Loading branch information
midlik committed May 29, 2024
1 parent 68320d1 commit d425dd2
Show file tree
Hide file tree
Showing 16 changed files with 311 additions and 251 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<!-- README for GitHub -->

# Heatmap Component
# HeatmapComponent

TypeScript library for creating interactive grid heatmaps.

The goal of Heatmap Component is to provide a tool for visualizing two-dimensional data in the form of grid heatmaps. It focuses on interactivity, performance, and customizability.
The goal of HeatmapComponent is to provide a tool for visualizing two-dimensional data in the form of grid heatmaps. It focuses on interactivity, performance, and customizability.

### Features

Expand All @@ -25,9 +25,9 @@ The goal of Heatmap Component is to provide a tool for visualizing two-dimension
- Axis labeling
- Data loading or modification via UI

### Example of Heatmap Component visualization
### Example of HeatmapComponent visualization

![Heatmap Component](./docs/heatmap-component.png)
![HeatmapComponent](./docs/heatmap-component.png)

### Live demos

Expand Down
2 changes: 1 addition & 1 deletion README.npm.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- README for NPM package -->

# Heatmap Component
# HeatmapComponent

TypeScript library for creating interactive grid heatmaps

Expand Down
2 changes: 1 addition & 1 deletion demo/minimal-example.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<title>HeatmapComponent minimal example</title>
<link rel="shortcut icon" type="image/x-icon" href="../favicon.ico">

<!-- Heatmap Component script and style -->
<!-- HeatmapComponent script and style -->
<!-- <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/heatmap-component@latest/build/heatmap-component.css" /> -->
<!-- <script src="https://cdn.jsdelivr.net/npm/heatmap-component@latest/build/heatmap-component.js"></script> -->

Expand Down
131 changes: 8 additions & 123 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,130 +1,15 @@
# Heatmap Component documentation
# HeatmapComponent documentation

Heatmap Component is an NPM package and can be used in two ways:
- [Quickstart guide](./quickstart.md)

- (Quick and dirty) Include pre-bundled JS and CSS in your HTML and initialize the heatmap using global `HeatmapComponent` object
- (Recommended) Add `heatmap-component` as a dependency and import what you want
- [Architecture](./architecture.md)

TODO: index of topics (Quickstart, customization, colors/scales, architecture/extensions)
- [Customization and extensions](./customization.md)

## Quickstart guide
- [Events](./events.md)

This Quickstart guide will walk you through setting up a basic heatmap visualization using the HeatmapComponent library.
- [Color scales](./color-scales.md)

### Step 1: Setup
- [Working with large data](./large-data.md)

First, make sure you include the HeatmapComponent script and styles in your HTML file.

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>HeatmapComponent minimal example</title>

<!-- HeatmapComponent script and style: -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/heatmap-component@latest/build/heatmap-component.css" />
<script src="https://cdn.jsdelivr.net/npm/heatmap-component@latest/build/heatmap-component.js"></script>
</head>
</html>
```

In this example we use pre-bundled JS and CSS files via jsDelivr CDN. You can replace `@latest` in the URLs by a specific version, e.g. `@1.0.0`, or use your own bundles (if using HeatmapComponent as a library).

Including the `heatmap-component.js` script creates a global object `HeatmapComponent` that we will use later.

### Step 2: Add a DIV element to hold the heatmap

Add a DIV element somewhere in the page and give it a custom `id` attribute. This DIV is where the heatmap will be rendered.

```html
<body>
<div id="app" style="width: 500px; height: 300px; border: solid gainsboro 1px;"></div>
<script>
// Your code will go here
</script>
</body>
</html>
```

### Step 3: Define data

Within the script section of the HTML file, we will define the data to be visualized.

First, we need to define the grid by providing the "names" for columns (X domain) and rows (Y domain). These values can be of any simple type (string, number, boolean, null); objects and undefined are not allowed.

Next, we define the data values that will be placed in the grid. We follow the D3.js convention and call each of these values a "datum" (plural: "data" or "data items"). Each datum can be of any type, including more complex types such as objects or arrays. However, `undefined` is not allowed as a datum. In this example, we provide 9 data, each being an object with col, row, and score.

Finally, we define how data are placed in the grid. We do this by providing `x` function, which takes a datum and returns a column name, and `y` function, which returns row name. These functions can also take a second parameter, which is the index of the datum (e.g. `x: (d, i) => i%nColumns, y: (d, i) => Math.floor(i/nColumns)` will place the data row-by-row). Alternatively, you can provide an array of row/column names instead of a function (first name belongs to the first datum etc.).

```js
const data = {
xDomain: [1, 2, 3, 4], // "names of columns"
yDomain: ['A', 'B', 'C'], // "names of rows"
data: [
{ col: 1, row: 'A', score: 0.0 },
{ col: 1, row: 'B', score: 0.2 },
{ col: 1, row: 'C', score: 0.4 },
{ col: 2, row: 'A', score: 0.6 },
{ col: 2, row: 'B', score: 0.8 },
{ col: 2, row: 'C', score: 1.0 },
{ col: 3, row: 'A', score: 0.3 },
{ col: 3, row: 'C', score: 0.7 },
{ col: 4, row: 'B', score: 0.5 },
],
x: d => d.col, // function that takes a datum and returns column name
y: d => d.row, // function that takes a datum and returns row name
};
```

### Step 4: Create a heatmap instance

Create a heatmap instance using the prepared data.

```js
const heatmap = HeatmapComponent.Heatmap.create(data);
```

You can also call `create()` with no argument and add data later via `heatmap.setData(data)`.

### Step 5: Customize

Customize the heatmap instance as needed. Typically, you'll want to specify coloring (default coloring assigns gray color to any datum). This is done by calling `setColor` method and providing a coloring function, which takes a datum and returns a color (can be a CSS color, e.g. `'green'`, `'#f00000'`, `'rgba(255,0,0,0.5)'`, or a package-specific [`Color`](./color-scales.md#color-encoding) type, encoding each color as a number for better performance). The coloring function can also take multiple parameters, being datum, column name, row name, column index, row index.

Use `HeatmapComponent.ColorScale.continuous` to create continuous color scales, `HeatmapComponent.ColorScale.discrete` for discrete (categorical) color scales (details [here](./color-scales.md)).

```js
const colorScale = HeatmapComponent.ColorScale.continuous('YlOrRd', [0, 1]); // yellow-orange-red color scale for values from 0 to 1
heatmap.setColor(d => colorScale(d.score)); // function that takes a datum and returns color
```

Another common feature, that has to be enabled explicitely is manual zooming:

```js
const colorScale = HeatmapComponent.ColorScale.continuous('YlOrRd', [0, 1]); // yellow-orange-red color scale for values from 0 to 1
heatmap.setColor(d => colorScale(d.score)); // function that takes a datum and returns color
```

### Step 6: Render heatmap

Finally, render the heatmap in the specified HTML element.

```js
heatmap.render('app');
```

### Step 7: Subscribe to events (optional)

You can subscribe to events emitted by the heatmap instance to integrate it with other parts of the page. All these events are RxJS BehaviorSubjects.

```js
heatmap.events.select.subscribe(e => console.log('Selected', e)); // fires when the user clicks a data cell
```

And that's it! You now have a basic heatmap visualization displayed in your web page.

Full HTML for this example [here](../demo/minimal-example.html)

Live demo: <https://pdbeurope.github.io/heatmap-component/demo/minimal-example.html>

TODO: plunkr live example?
Back to the main [project README](../README.md)
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# HeatmapComponent library architecture
# HeatmapComponent – Library architecture

HeatmapComponent uses a modular architecture designed to provide flexible functionality for creating and customizing heatmap visualizations. At the core of the package is the HeatmapCore class, to which more functionality is added through extensions.

Expand Down
4 changes: 2 additions & 2 deletions docs/color-scales.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# HeatmapComponent color scales
# HeatmapComponent – Color scales

[**ColorScale**](../src/heatmap-component/data/color-scale.ts) constant provides useful functions for creating continuous and discrete (categorical) color scales.

Expand Down Expand Up @@ -55,7 +55,7 @@ The difference is that scales from `ColorScale` work with numeric color encoding

### Color encoding

The numeric color encoding used by Heatmap Component represents each color by a 32-bit integer (type `Color` is alias for `number`):
The numeric color encoding used by HeatmapComponent represents each color by a 32-bit integer (type `Color` is alias for `number`):

```
AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB
Expand Down
58 changes: 9 additions & 49 deletions docs/customization.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# HeatmapComponent customization
# HeatmapComponent – Customization

## Data

Expand Down Expand Up @@ -58,8 +58,6 @@ heatmap.setDomains([1, 2, 3, 4], ['A', 'B', 'C']); // reset original domains

This method can be used for showing/hiding individual data cells without changing the underlying heatmap data. This is achieved by providing a **filter function**. This function will be executed for each non-empty cell and only cells where is returns `true` will be shown.

// TODO: link to Provider functions?

```ts
setFilter(filter: ((datum: TDatum, x: TX, y: TY, xIndex: number, yIndex: number) => boolean) | undefined): this

Expand Down Expand Up @@ -176,27 +174,27 @@ console.log(heatmap.getZoom());
// Returns:
// {
// xMinIndex: -0.5, xMaxIndex: 3.5, yMinIndex: -0.5, yMaxIndex: 2.5,
// xMin: 0.5, xMax: 4.5,
// xFirstVisibleIndex: 0, xLastVisibleIndex: 3, yFirstVisibleIndex: 0, yLastVisibleIndex: 2,
// xMin: 0.5, xMax: 4.5,
// xFirstVisible: 1, xLastVisible: 4, yFirstVisible: "A", yLastVisible: "C"
// }
```

TODO: short intro about index-based vs name-based values
The `ZoomEventValue` object returned by this method contains the information about the edges of the viewport in relation to the visualized data. It provides two sets of properties: index-based properties (having `Index` suffix) use column and row indices counted from 0 (in our example, it's columns 0, 1, 2, 3, and rows 0, 1, 2); value-based properties (without `Index` suffix) use the values ("names") assigned to the columns and rows (in our example, it's columns 1, 2, 3, 4 and rows 'A', 'B', 'C').

`xMinIndex, xMaxIndex, yMinIndex, yMaxIndex` are continuous column/row indices corresponding to the left/right/top/bottom edge of the viewport. These values depend on current alignment settings (see [`setAlignment`](#setAlignment)).

`xMin, xMax, yMin, yMax` are continuous X/Y values corresponding to the left/right/top/bottom edge of the viewport. These values are only available if the column/row names are numbers in either increasing or decreasing order. These values depend on current alignment settings (see [`setAlignment`](#setAlignment)).

`xFirstVisibleIndex, xLastVisibleIndex, yFirstVisibleIndex, yLastVisibleIndex` are indices of the first/last column/row that is at least partially visible.

`xMin, xMax, yMin, yMax` are continuous X/Y values corresponding to the left/right/top/bottom edge of the viewport. These values are only available if the column/row names are numbers in either increasing or decreasing order. These values depend on current alignment settings (see [`setAlignment`](#setAlignment)).

`xFirstVisible, xLastVisible, yFirstVisible, yLastVisible` are the names of the first/last column/row that is at least partially visible.

### `zoom`

This method is used to change the zoom state of the heatmap. It is always enabled, regardless of the manual zooming settings. Returns the zoom state after the requested change (this is not necessarily the same as the requested zoom state, because of the restrictions on zoom scale and translation), or undefined if the heatmap is not rendered yet.

The properties of the `request` parameter have the same meaning as those returned by `getZoom` but at most one value should be provided to specify each edge (left/right/top/bottom) of the zoomed area (e.g. do not provide `xMin` and `xFirstVisible` at the same time as they would conflict). If no value is provided for any of the edges, this edge will be set to the outermost available position (i.e. zoom "from the beginning" / "to the end") – this can be used to fully zoom out horizontally, vertically, or both. Note that `xMin, xMax, yMin, yMax` can only be used if the column/row names are numbers in either increasing or decreasing order.
The properties of the `request` parameter have the same meaning as those returned by `getZoom` but at most one property should be provided to specify each edge (left/right/top/bottom) of the zoomed area (e.g. do not provide `xMin` and `xFirstVisible` at the same time as they would conflict). If no property is provided for any of the edges, this edge will be set to the outermost available position (i.e. zoom "from the beginning" / "to the end") – this can be used to fully zoom out horizontally, vertically, or both. Interpretation of some properties depends on current alignment settings, in the same way as with `getZoom` (see [`setAlignment`](#setAlignment)). Note that `xMin, xMax, yMin, yMax` can only be used if the column/row names are numbers in either increasing or decreasing order.

```ts
zoom(request: Partial<ZoomEventValue<TX, TY>> | undefined): ZoomEventValue<TX, TY> | undefined
Expand All @@ -213,9 +211,9 @@ heatmap.zoom(undefined); // Reset zoom (zoom out)

### `setAlignment`

This method controls how column/row indices and names are aligned to X and Y axes, when using `getZoom` and `zoom` methods and `zoom` event.
This method controls how column/row indices and names are aligned to X and Y axes, when using `getZoom` method, `zoom` method, and `zoom` event.

Let's demonstrate this on an example of 4 columns, corresponding to X values ("column names") 1, 2, 3, 4. Column indices are always 0-based, so 0, 1, 2, 3.
Let's demonstrate this on our example with 4 columns, corresponding to X values ("column names") 1, 2, 3, 4. Column indices are always 0-based, so 0, 1, 2, 3.

Default alignment is `'center'`, so the reported value is aligned with the center of the column:

Expand Down Expand Up @@ -277,47 +275,9 @@ Note: Some of the extension parameters are also exposed via other methods, e.g.

### Custom extensions

Users of Heatmap Component can implement their own extensions, following the example of existing extensions (see [/src/heatmap-component/extensions](../src/heatmap-component/extensions/)).
Users of HeatmapComponent can implement their own extensions, following the example of existing extensions (see [/src/heatmap-component/extensions](../src/heatmap-component/extensions/)).
These extensions can then be registered by:

```ts
heatmap.registerExtension(CustomExtension, { ...parameterValues }); // extension parameter values are optional
```

TODO: implement one example extension?
TODO: add Behavior onUnregister?

---

## Events

Heatmap Component provides several event that the users can subscribe to. All of these are RxJS `BehaviorSubject`, so they emit the current value to new subscribers.

- `hover`: Fires when the user hovers over the component.
- `select`: Fires when the user selects/deselects a cell (e.g. by clicking on it).
- `zoom`: Fires when the component is zoomed in or out, or panned (translated).
- `resize`: Fires when the window is resized. Subject value is the size of the canvas in pixels.
- `data`: Fires when the visualized data change (including filter or domain change).
- `render`: Fires when the component is initially rendered in a div.

```ts
// Example usage:
heatmap.events.select.subscribe(e => {
console.log('selecting:', e);
});

// Example usage from extension code (automatically unsubscribes on unregister)
this.subscribe(this.state.events.select, e => {
console.log('selecting:', e);
});
```

TODO: continue here

---

## Work with large data

TODO:

...?
27 changes: 27 additions & 0 deletions docs/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# HeatmapComponent – Events

HeatmapComponent provides several events that the users can subscribe to. All of these are RxJS `BehaviorSubject`, so they emit the current value to new subscribers.

- `hover`: Fires when the user hovers over the component. Subject value holds information about the hovered cell.

- `select`: Fires when the user selects/deselects a cell (e.g. by clicking on it). Subject value holds information about the selected cell.

- `zoom`: Fires when the component is zoomed in or out, or panned (translated). Subject value is the same as what [`getZoom`](./customization.md#getZoom) returns.

- `resize`: Fires when the window is resized. Subject value is the size of the canvas in pixels.

- `data`: Fires when the visualized data change (including filter or domain change).

- `render`: Fires when the component is initially rendered in a div.

```ts
// Example usage:
heatmap.events.select.subscribe(e => {
console.log('selecting:', e);
});

// Example usage from extension code (automatically unsubscribes on unregister)
this.subscribe(this.state.events.select, e => {
console.log('selecting:', e);
});
```
49 changes: 49 additions & 0 deletions docs/large-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# HeatmapComponent – Working with large data

HeatmapComponent aims to provide performant visualization even for large datasets. However, there are a few things to keep in mind when working with large datasets.

- Use only simple data types as datum type (`number`, `string`, `boolean`). Using `object` as datum type can significantly slow down the visualization.

```ts
// Potentially slow:
const heatmap = Heatmap.create({
xDomain: [0, 1, 2, ..., nColumns-1],
yDomain: [0, 1, 2, ..., nRows-1],
data: [{ row: 0, col: 0, value: 0.1 }, { row: 0, col: 1, value: 0.2 }, { row: 0, col: 2, value: 0.3 }, ...],
x: d => d.col,
y: d => d.row,
});

// Better:
const heatmap = Heatmap.create({
xDomain: [0, 1, 2, ..., nColumns-1],
yDomain: [0, 1, 2, ..., nRows-1],
data: [0.1, 0.2, 0.3, ...], // Yet better: use Float32Array
x: (d, i) => i % nColumns,
y: (d, i) => Math.floor(i / nColumns),
});
```

Note: Providing the data in a `Float32Array`, `Int16Array` etc. instead of a standard `Array` may be faster. However, the current implementation of HeatmapComponent still uses standard `Array` internally.

- Use a coloring function that returns type `Color` (not string), and avoid complex operations and conversions to and from string within the body of the coloring function. You can use [`ColorScale`](./color-scales.md) to create a coloring function, or call `Color.fromRgb` to create color within the body of the coloring function. `ColorScale` is optimized for use with HeatmapComponent and is usually faster then color scales from D3.

```ts
// Potentially slow:
hm.setColor(d3.scaleSequential(d3.interpolateSpectral).domain([0, 1]));
// Better:
hm.setColor(ColorScale.continuous('Spectral', [0, 1]));
hm.setColor(ColorScale.continuous([0, 1], ['white', '#ff00ff']));
// Potentially slow:
hm.setColor(d => (d < 0.5 ? '#000000' : 'yellow'));
// Better:
hm.setColor(d => (d < 0.5 ? Color.fromRgb(0, 0, 0) : Color.fromRgb(255, 255, 0)));
// Even better:
const black = Color.fromString('#000000');
const yellow = Color.fromString('yellow');
hm.setColor(d => (d < 0.5 ? black : yellow));
```
Loading

0 comments on commit d425dd2

Please sign in to comment.