# Local coordinates

Understanding how local coordinates work is key to understanding `florence`. The `Graphic`, `Section` and `Glyph` components can create local coordinate systems, and all other components are affected by local coordinate systems.

## Creating local coordinate systems

As stated above, local coordinate systems are created with the `Graphic` and `Section` components. The following props are used to create local coordinate systems:

Prop Required Type(s) Default Unit(s)
scaleX `false` `Function` `undefined` d3 scale
scaleY `false` `Function` `undefined` d3 scale
coordinates `false` `CoordinateTransformation` `cartesian()` -
flipX `false` `Boolean` `false` -
flipY `false` `Boolean` `false` -
padding `false` `Number`, `Object` `undefined` Pixel
zoomIdentity `false` `Object` `undefined` -

The purpose of a local coordinate system is to convert numerical values, given to components within a `Graphic`, `Section` or `Glyph`, to pixel values that end up on your screen. This is a process that happens in all visualizations, and is often referred to as scaling in the grammar of graphics. All marks and layers have so-called ‘positioning props’ for this purpose. Below is a schematic representation of how `florence` converts local coordinates to pixel values: The three main steps in this process are:

1. Scaling
2. Applying the coordinate transformation, if any
3. Applying the ‘final’ transformation, which encompasses padding, zooming and flipping.

These three concepts will be explained in depth in the next paragraphs. While these can conceptually be seen as seperate steps, internally `florence` tries to combine them for performance reasons.

## Scaling

### Basics

Scaling is the process of “mapping a dimension of abstract data to a visual representation”. In `florence`, this is not always entirely true. The output of the scaling step is not necessarily the final ‘visual representation’ (a pixel value). If the user chooses to apply a coordinate transformation, the output of the scaling step goes through another step of processing. Or, if the user specifies the `padding`, `flipX`, `flipY` or `zoomIdentity` props, there will be another (‘final’) step before we obtain a pixel value. But for now, let’s forget about coordinate transformations and things like padding and flipping, and look at how scaling works in isolation.

### Default coordinates

If the `scaleX` and `scaleY` props are not provided, `florence` falls back to so-called ‘default coordinates’. These coordinates have an extent of `[0, 1]` in both dimensions. What this means in practice is that an x-coordinate of `0` is all the way on the left, and `1` is all the way on the right, while `0.5` is exactly in the middle. For the y-coordinate, `0` would be the top and `1` the bottom. See the example below:

``````<script>
import { Graphic, PointLayer } from '@snlab/florence'
</script>

<Graphic width={200} height={200}>
<PointLayer
x={[0, 0, 1, 1, 0.5]}
y={[0, 1, 1, 0, 0.5]}
fill={['red', 'blue', 'green', 'orange', 'purple']}
/>
</Graphic>``````

### Providing scales

To scale coordinates in a local coordinate system, pass a d3 scale to `scaleX` or `scaleY`. D3 scales are functions that map domains to ranges, where a domain is the ‘abstract data dimension’ and the range is the output dimension – in this case, pixels on your screen. In `florence`, the range will be taken care of for you, so you only need to define the domain.

Say that we have the following data:

``const points = { x: [0, 5, 10], y: [0, 5, 10] }``

And we want to use this data to render three points that fit exactly in the `Graphic`. One way of doing this would be to write a function that scales them down to the ‘default coordinates’ discussed above, by dividing all coordinates by 10:

``const points = { x: [0, 0.5, 1], y: [0, 0.5, 1] }``

But `scaleX` and `scaleY` provide an alternative method: creating a local coordinate system. If we want to use the original data dimensions, we can use our data directly in the `Graphic` using d3’s scaleLinear. We will also give the `Graphic` a background color. This makes it easier to see where the points end up.

``````<script>
import { Graphic, PointLayer } from '@snlab/florence'
import { scaleLinear } from 'd3-scale'

const points = { x: [0, 5, 10], y: [0, 5, 10] }
</script>

<Graphic
width={200}
height={200}
scaleX={scaleLinear().domain([0, 10])}
scaleY={scaleLinear().domain([0, 10])}
backgroundColor="#b2ffb2"
>

<PointLayer x={points.x} y={points.y} radius={10} fill="red" />

</Graphic>``````

### Non-numeric data

Scaling can also be used to convert `String`s or `Date`s, as long as the correct d3 scale is chosen.

``````<script>
import { Graphic, Rectangle } from '@snlab/florence'
import { scalePoint, scaleTime } from 'd3-scale'

const domainX = ['a', 'b', 'c', 'd', 'e']
const domainY = [new Date(2020, 1, 1), new Date(2020, 1, 10)])
</script>

<Graphic
width={200}
height={200}
scaleX={scalePoint().domain(domainX)}
scaleY={scaleTime().domain(domainY)}
>

<Rectangle
x1={'a'} x2={'e'}
y1={new Date(2020, 1, 1)} y2={new Date(2020, 1, 5)}
fill={'blue'}
opacity={0.5}
/>

<Rectangle
x1={'b'} x2={'d'}
y1={new Date(2020, 1, 3)} y2={new Date(2020, 1, 10)}
fill={'red'}
opacity={0.5}
/>

</Graphic>``````

### Domain shorthand

It is also possible to simply pass a numeric domain to `scaleX` or `scaleY`. In this case, `florence` will create a linear scale from the domain. Therefore this:

``<Graphic scaleX={[0, 10]}>``

is equal to this:

``<Graphic scaleX={scaleLinear().domain([0, 10])}>``

### Function syntax

Even when `scaleX` and `scaleY` are used, it is possible to bypass them and position something in default coordinates. This is useful for, for example, annotations. To do this, pass the positioning prop a function that returns a default coordinate, instead of a value in the local coordinate system:

``````<script>
import { Graphic, Point, Label } from '@snlab/florence'
import { scaleLinear } from 'd3-scale'

const point = { x: 5, y: 5 }
</script>

<Graphic
width={200}
height={200}
scaleX={scaleLinear().domain([0, 10])}
scaleY={scaleLinear().domain([0, 10])}
backgroundColor={'#b2ffb2'}
>

<Point x={point.x} y={point.y} radius={20} fill={'red'} />

<Label x={() => 0.5} y={() => 0.5} text={'I am in the middle!'} />

</Graphic>``````

## Coordinate transformation

Right now, `florence` only supports two coordinate transformations: `cartesian` (which is the default), and `polar`. Coordinate systems are set using the `coordinates` prop. For example:

``````<script>
import { Graphic, polar } from '@snlab/florence'
</script>

<Graphic
width={500}
height={500}
coordinates={polar()}
>``````

## Final transformation

The last step in going from local coordinates to pixel values is the ‘final’ transformation. Again, while `florence` internally tries to combine different steps to improve performance, conceptually this ‘final’ step can be divided into three sub-steps: flipping, padding and zooming.

### Flipping

‘Flipping’ refers to using the `flipX` and `flipY` props to ‘flip’ to inverse the final output. `flipY` is particularly useful. Web coordinates run from top to bottom by default, but when making graphics, we usually expect coordinates to run from bottom to top. `flipY` provides a convenient way to invert this behavior.

Without using `flipX` and `flipY`, the following point will be in the top left corner:

``````...

<Graphic
width={200}
height={200}
backgroundColor={'#b2ffb2'}
>
</Graphic>``````

With `flipX` and `flipY`, it is moved to the bottom right corner instead:

``````...

<Graphic
width={200}
height={200}
flipX
flipY
backgroundColor={'#b2ffb2'}
>
</Graphic>``````

Padding is used to create space between the `Graphic` or `Section`’s outer borders and their contents. The `padding` prop can be used in to ways: by giving it a `Number` or an `Object`. If given an `Object`, it must have the following structure:

``````{
left: <Number>,
right: <Number>,
top: <Number>,
bottom: <Number>
}``````

If given a `Number`, for example `20`, the padding will internally be converted to

``````{
left: 20,
right: 20,
top: 20,
bottom: 20
}``````

Furthermore, just providing some of the four possible members of the padding object, e.g. only `left`:

``{ left: 10 }``

will set the rest to zero:

``````{
left: 10,
right: 0,
top: 0,
bottom: 0
}``````

An example:

``````<script>
import { Graphic, Section, Rectangle } from '@snlab/florence'
</script>

<Graphic
width={200}
height={200}
backgroundColor={'blue'}
>
<Rectangle x1={0} x2={1} y1={0} y2={1} fill={'red'} />
</Section>
</Graphic>``````

### Zooming

`Graphic`s, `Section`s and `Glyph`s can be zoomed using the `zoomIdentity` prop. `zoomIdentity` has to be an `Object` with the following structure:

``````{
x: <Number>,
y: <Number>,
kx: <Number>,
ky: <Number>
}``````

Where `x` is the translation in the x dimension (higher = further to the right), `y` is the translation in the y dimension (higher = further down), and `kx` and `ky` are the zooming factors in respectively the x and y dimensions. `x` and `y` are in pixel values. The identity transformation is

``````{
x: 0,
y: 0,
kx: 1,
ky: 1
}``````

An example that uses a `tweened` store for zooming:

``````<script>
import { Graphic, Section, RectangleLayer } from '@snlab/florence'
import { scaleLinear } from 'd3-scale'
import { tweened } from 'svelte/motion'
import { cubicOut } from 'svelte/easing'

const values = Array(5).fill(0).map((_, i) => i)
const x1 = values.reduce(acc => [...acc, ...values], [])
const x2 = x1.map(v => v + 1)
const y1 = values.reduce((acc, v) => [...acc, ...Array(5).fill(v)], [])
const y2 = y1.map(v => v + 1)

const scale = scaleLinear().domain([0, 8]).range(['blue', 'red'])
const fill = x1.map((x, i) => scale(x + y1[i]))

const k = tweened(1, { easing: cubicOut })
setInterval(() => \$k === 1 ? k.set(5) : k.set(1), 1400)
</script>

<Graphic
width={200}
height={200}
scaleX={[0, 5]}
scaleY={[0, 5]}
zoomIdentity={{ x: 0, y: 0, kx: \$k, ky: \$k }}
>
<RectangleLayer {x1} {x2} {y1} {y2} {fill} />
</Graphic>``````

## Nesting Sections

In all the examples above, `Graphic` components were used. While this is fine for simple graphics, more complicated graphics will often require multiple coordinate systems. This can be accomplished by using `Section`s. `Section`s can be positioned in the same way as `Rectangle`s – see the Section documentation. The `Section` can then be used to define a new local coordinate system. `Section`s can even be nested. However, a `Section` that has polar coordinate transformation cannot contain any other `Section`s.

An example:

``````<script>
import { Graphic, Section, Point } from '@snlab/florence'
import { scaleLinear } from 'd3-scale'
</script>

<Graphic
width={500}
height={500}
>

<Section
y1={0}
y2={0.5}
scaleX={[0, 10]}
scaleY={[0, 10]}
backgroundColor={'#ffcccb'}
>

<Point x={5} y={5} radius={10} fill={'red'} />

<Section
x1={0}
x2={5}
y1={0}
y2={5}
scaleX={[0, 10]}
scaleY={[0, 10]}
backgroundColor={'#bcf5bc'}
>

<Point x={5} y={5} radius={10} fill={'green'} />

</Section>

</Section>

<Section
y1={0.5}
y2={1}
scaleX={[50, 60]}
scaleY={[50, 60]}