## Build for docker

```bash
yarn run build:storybook
```

## Build and run docker

```bash
docker build -t eq:ui .
docker run --rm --name ui -p 3000:80 eq:ui
```

## Version

```bash
yarn workspace @equilab/ui version
```

## D3 cheatsheet

### Basics

#### Add to page

```html
<script src="https://d3js.org/d3.v6.min.js"></script>
```

#### View version

```javascript
d3.version;
```

#### Selection methods

```ignorelang
d3.select
d3.selectAll
```

#### Accessing nodes

```javascript
selection.nodes()
selection.node()

d3.selectAll("li").nodes()
Array(4) [li, li, li, li
  ]

d3.selectAll("li").node()
< li >

d3.select("#page-title").style("background-color", "#000000").style("color", "#ffffff").attr("class", "new-class").text("D3 is coool!")
Object
{
  _groups: (1) […],
  _parents: (1) […]
}
```

Default parent is `html`

```javascript
d3.selectAll("p");
// will have 2 keys
// _parents
// _groups
```

But after data join:

```javascript
d3.selectAll("p").data([1, 2, 3]);
// will have 4 keys
// _parents
// _groups
// _enter
// _exit
```

#### Manipulating selections

```ignorelang
selection.style(property[,newValue])
selection.attr(attribute[,newValue])
selection.text([newValue])
selection.html([newValue])
selection.classed(classList[, shouldClassesBeSet])
selection.append(tagName)
selection.property(name[, newValue])
selection.remove()
```

### Selections and callbacks

The callback will be called once per element in selection

```ignorelang
selection.style(property[,callback])
selection.attr(attribute[,callback])
selection.text([callback])
selection.html([callback])
```

```javascript
d3.selectAll("li").style("font-size", function () {
  return Math.random() * 40 + "px";
});
```

Callback structure

```ignorelang
function (_, idx) {
  // code
}
```

### Event listeners

```javascript
selection.on(event, callback);
```

```javascript
d3.select("h1").on("click", function () {
  console.log(".!.");
});
d3.select("h1").on("click", null);
d3.select("#new-note").on("submit", function () {
  d3.event.preventDefault();
  const input = d3.select("input");
  d3.select("#notes")
    .append("p")
    .classed("note", true)
    .text(input.property("value"));
  input.property("value", "");
});
```

### Join data and enter selection

To join dom elements and data use `data(d)`

After joining we can use `enter()` which returns enter selection - elements that need to be added

It's usually followed by `.append("div")` which adds elements to the DOM

```javascript
d3.select("#quotes")
  .style("list-style", "none")
  .selectAll("li")
  .data(quotes)
  .enter()
  .append("li")
  .text(function (d) {
    return d.quote;
  });
```

IMPORTANT! By default selection maps to INDEX. You need to use key function for correct mapping

### Exit selection and key function

Key function is used to join dom elements and data

`exit()` selection contains elements that need to be removed from the DOM

```ignorelang
selection.data(dataArr[, keyFunction])
```

```javascript
d3.selectAll("li")
  .data(
    quotes.filter((el) => el.rating !== "R"),
    (d) => d.quote,
  )
  .exit()
  .remove();
```

`d => d.quote` is the key function

### Selection types

- `enter()` data with no elements
- `exit()` elements with no data
- `update()` data and elements

### Merging selections

```javascript
selection.merge(otherSelection);
```

### General update pattern

1. Grab update selection, make changes unique to that selection, store in var
2. Grab exit selection, remove elements
3. Grab enter selection, make changes to it
4. Merge enter and update selections, make changes for both of them

### SVG

Scalable vector graphics

```javascript
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"></svg>
```

In SVG Y-axis is reversed. Upper left corner = (0,0)

#### Line

```javascript
<line x1="100" y1="100" x2="700" y2="350" stroke-width="5px" stroke="blue" />
```

#### Group

`g` elements

```javascript
<g stroke-width="5px" stroke="blue">
  <line x1="100" y1="100" x2="700" y2="350" />
  <line x1="100" y1="350" x2="700" y2="100" />
</g>
```

#### Rectangle

- `x` of upper-left corner
- `y` of upper-left corner
- `width`
- `height`
- `stroke`
- `stroke-width`
- `fill` interior color
- `rx` and `ry` for rounding corners

```javascript
<rect
  x="50"
  y="50"
  width="300"
  height="200"
  fill="#ffc107"
  stroke="#2196f3"
  stroke-width="8px"
/>
```

#### Polygon

```javascript
 <polygon points="100,200 400,200, 400,500 100,500" fill="#e91e63" stroke="#795548" stroke-width="8px"/>
<svg
  version="1.1"
  baseProfile="full"
  xmlns="http://www.w3.org/2000/svg">
  <polygon
    fill="yellow"
    stroke="black"
    stroke-width="8px"
    points="400,21.5 450.5,177 614,177 481.5,273 532.5,428.5 400,332.5 267.5,428.5, 318.5,273 186,177 349.5,177"/>
```

#### Circle

- `cx`
- `cy`
- `r`

```javascript
<circle cx="400" cy="235" r="65" fill="yellow" />
```

#### Text

- `x` and `y` of bottom left corner
- `dx` and `dy` to shift from anchor point
- `text-anchor`: `start`/`middle`/`end`
- `font-size`
- `font-family`
- `fill`
- `stroke-width`
- `stroke`
- `transform`: `rotate(degree x,y)` to rotate

```javascript
<text x="400" y="430" text-anchor="middle">
  Star
</text>
```

```javascript
<text
  x="400"
  y="430"
  text-anchor="middle"
  font-size="2em"
  font-family="sans-serif"
  fill="white"
  stroke="black"
  transform="rotate(-10 400,430)"
>
  Star
</text>
```

#### Path element

- `d` attribute commands
- `M` move (`M X Y`)
- `L` line (`L X Y`)
- `Z` close path with a line
- `H` draw horizonal line
- `V` draw vertical line
- `Q` quadratic Bezier curve
- `C` cubic Bezier curve
- `A` circular arc

```javascript
<path d="M400 100 L500 300 L300 300 L400 100" />
```

- UPPERCASE X Y - absolute X and Y
- lowercase X Y - relative to current position

```javascript
  <path d="M400 100 l100 200 l-200 0 l100 -200"/>
<path d="M400 100 l100 200 l-200 0 z"/>
```

### Extreme values

```ignorelang
    d3.max(dataArr [, callback])
    d3.min(dataArr [, callback])
    d3.max([1,2,3,4,5])
    5
    d3.min([1,2,3,4,5])
    1l
```

callback is used to map comparable key

```ignorelang
const people = [{name: "Brett", age: 40}, {name: "Pit", age: 30}, {name: "Alice", age: 70}]
undefined
d3.min(people, d => d.age)
30
d3.max(people, d => d.name.length)
5
const [min, max] = d3.extent(birthData2011, d => d.lifeExpectancy)
[48, 83]
```

### Scales

```ignorelang
d3.scaleLinear()
    .domain([n1,n2])
    .range([n3,n4])
```

Scales values in domain to values in range.

```ignorelang
const scale = d3.scaleLinear().domain([1,17]).range([-4,52])
undefined
scale(1)
-4
scale(17)
52
scale(9)
24
```

Example with scales

```javascript
const MIN_YEAR = d3.min(birthData, (d) => d.year);
const MAX_YEAR = d3.max(birthData, (d) => d.year);
const WIDTH = 600;
const HEIGHT = 600;
const BAR_PADDING = 10;
const BARS_COUNT = 12;
const BAR_WIDTH = WIDTH / BARS_COUNT - BAR_PADDING;
const MAX_BIRTHS = d3.max(birthData, (d) => d.births);

const yScale = d3.scaleLinear().domain([0, MAX_BIRTHS]).range([HEIGHT, 0]);

d3.select("input")
  .property("min", MIN_YEAR)
  .property("max", MAX_YEAR)
  .property("value", MIN_YEAR);

d3.select("svg")
  .attr("width", WIDTH)
  .attr("height", HEIGHT)
  .selectAll("rect")
  .data(birthData.filter((el) => el.year === MIN_YEAR))
  .enter()
  .append("rect")
  .attr("width", BAR_WIDTH)
  .attr("height", (d) => HEIGHT - yScale(d.births))
  .attr("y", (d) => yScale(d.births))
  .attr("x", (d, i) => i * (BAR_WIDTH + BAR_PADDING))
  .attr("fill", "purple");

d3.select("input").on("input", () => {
  const year = +d3.event.target.value;
  d3.selectAll("rect")
    .data(birthData.filter((el) => el.year === year))
    .attr("height", (d) => HEIGHT - yScale(d.births))
    .attr("y", (d) => yScale(d.births));
});
```

### Axes

```ignorelang
d3.axisTop(scale)
d3.axisBottom(scale)
d3.axisLeft(scale)
d3.axisRight(scale)
```

```javascript
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
d3.select("svg")
  .append("g")
  .call(xAxis)
  .attr("transform", "translate(0, " + (HEIGHT - PADDING) + ")");

d3.select("svg")
  .append("g")
  .call(yAxis)
  .attr("transform", "translate(" + PADDING + ", 0)");
```

To draw grid using ticks:

```javascript
const xAxis = d3.axisBottom(xScale).tickSize(-HEIGHT + 2 * PADDING);
const yAxis = d3.axisLeft(yScale).tickSize(-WIDTH + 2 * PADDING);
```

Outer tick size can be set separately

```javascript
const xAxis = d3
  .axisBottom(xScale)
  .tickSize(-HEIGHT + 2 * PADDING)
  .tickSizeOuter(0);
const yAxis = d3
  .axisLeft(yScale)
  .tickSize(-WIDTH + 2 * PADDING)
  .tickSizeOuter(0);
```

Ticks can be styled via css

```css
.tick line {
  stroke: #ccc;
  stroke-dasharray: 10, 5;
}
```

### Graph and axis headers

Graph header example

```javascript
d3.select("svg")
  .append("text")
  .attr("x", WIDTH / 2)
  .attr("y", PADDING)
  .style("text-anchor", "middle")
  .style("font-size", "1.5em")
  .text("Data on Births by Country in 2011");
```

X axis header example

```javascript
d3.select("svg")
  .append("text")
  .attr("x", WIDTH / 2)
  .attr("y", HEIGHT - PADDING)
  .attr("dy", "1.5em")
  .style("text-anchor", "middle")
  .text("Births per Capita");
```

Y axis header with rotation

```javascript
d3.select("svg")
  .append("text")
  .attr("transform", "rotate(-90)")
  .attr("x", -HEIGHT / 2)
  .attr("y", PADDING)
  .attr("dy", "-1.1em")
  .style("text-anchor", "middle")
  .text("Life Expectancy");
```

### Histograms

```javascript
const histogram = d3.histogram().value((d) => d.births);
const bins = histogram(yearData);
histogram.thresholds([90000, 180000, 270000]); // defines number of bins
histogram.thresholds(xScale.ticks());
// scale.ticks([count])
// returns approx count intermediate values from scale's domain
```
