@awesomephant/reactcake 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +1 -1
- package/lib/Html/index.tsx +11 -8
- package/lib/ScaledSvg/index.tsx +2 -2
- package/lib/Svg/index.tsx +2 -2
- package/lib/types.ts +4 -0
- package/notes.md +10 -0
- package/package.json +1 -1
- package/site/astro.config.mjs +9 -3
- package/site/package.json +1 -0
- package/site/src/components/Example.astro +2 -1
- package/site/src/examples/AxisX/AxisX.tsx +106 -0
- package/site/src/examples/AxisX/styles.module.css +36 -0
- package/site/src/examples/AxisY/index.tsx +76 -0
- package/site/src/examples/AxisY/styles.module.css +33 -0
- package/site/src/examples/ColumnChart.tsx +34 -17
- package/site/src/examples/Columns.tsx +17 -19
- package/site/src/examples/DebugPadding.tsx +17 -0
- package/site/src/examples/Gantt.tsx +39 -0
- package/site/src/examples/OrdinalKey/index.tsx +55 -0
- package/site/src/examples/OrdinalKey/styles.css +47 -0
- package/site/src/layouts/Base.astro +27 -14
- package/site/src/layouts/code.css +37 -0
- package/site/src/pages/index.mdx +5 -5
- package/site/tsconfig.json +1 -1
- package/site/src/examples/AxisX.tsx +0 -1
package/Readme.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A headless chart framework for React inspired by [Layercake](https://layercake.graphics/). See [documentation](https://reactcake.maxkohler.com/) for usage and motivation.
|
|
4
4
|
|
|
5
|
-
[](https://github.com/awesomephant/reactcake/actions/workflows/release.yml)
|
|
5
|
+
 [](https://github.com/awesomephant/reactcake/actions/workflows/release.yml)
|
|
6
6
|
|
|
7
7
|
## Contributing
|
|
8
8
|
|
package/lib/Html/index.tsx
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
+
import { useReactCake } from "../ReactCake";
|
|
3
|
+
import { LayoutProps } from "../types";
|
|
2
4
|
|
|
3
|
-
interface HtmlProps {
|
|
5
|
+
interface HtmlProps extends LayoutProps {
|
|
4
6
|
children?: ReactNode;
|
|
5
7
|
}
|
|
6
8
|
|
|
7
|
-
const Html = ({ children }: HtmlProps) => {
|
|
9
|
+
const Html = ({ overflow = "visible", children }: HtmlProps) => {
|
|
10
|
+
const { padding } = useReactCake();
|
|
11
|
+
|
|
8
12
|
return (
|
|
9
13
|
<div
|
|
10
14
|
className="reactcake-html"
|
|
11
15
|
style={{
|
|
12
16
|
position: "absolute",
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
bottom: 0,
|
|
17
|
+
top: `${padding.top}px`,
|
|
18
|
+
left: `${padding.left}px`,
|
|
19
|
+
right: `${padding.right}px`,
|
|
20
|
+
bottom: `${padding.bottom}px`,
|
|
21
|
+
overflow,
|
|
19
22
|
}}
|
|
20
23
|
>
|
|
21
24
|
{children}
|
package/lib/ScaledSvg/index.tsx
CHANGED
package/lib/Svg/index.tsx
CHANGED
package/lib/types.ts
CHANGED
package/notes.md
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
1
|
- Anything you draw past the edges shows up in the padding, the way it does on the other layouts. (https://layercake.graphics/guide#canvas)
|
|
2
|
+
|
|
3
|
+
from: https://layercake.graphics/guide
|
|
4
|
+
|
|
5
|
+
- it reverses the default range from [0, height] to [height, 0] in order to match the DOM coordinate system and make drawing easier out of the box.
|
|
6
|
+
- The default ranges for each dimension are:
|
|
7
|
+
|
|
8
|
+
x: [0, width]
|
|
9
|
+
y: [height, 0], unless using a scaleBand or scalePoint and it is [0, height]
|
|
10
|
+
r: [1, 25]. The min is 1 and not zero because this is a scaleSqrt by default and 0 is not allowed in that type of chart.
|
|
11
|
+
z: [0, width]
|
package/package.json
CHANGED
package/site/astro.config.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { defineConfig } from 'astro/config';
|
|
3
|
+
import {transformerRenderIndentGuides, transformerRenderLineNumber} from "@shikijs/transformers"
|
|
3
4
|
|
|
4
5
|
import mdx from '@astrojs/mdx';
|
|
5
6
|
|
|
@@ -7,9 +8,14 @@ import react from '@astrojs/react';
|
|
|
7
8
|
|
|
8
9
|
export default defineConfig({
|
|
9
10
|
markdown: {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
shikiConfig: {
|
|
12
|
+
theme: 'css-variables',
|
|
13
|
+
defaultColor: false,
|
|
14
|
+
transformers:[transformerRenderLineNumber(), transformerRenderIndentGuides()]
|
|
15
|
+
},
|
|
16
|
+
syntaxHighlight: {
|
|
17
|
+
type: 'shiki',
|
|
18
|
+
},
|
|
13
19
|
},
|
|
14
20
|
|
|
15
21
|
integrations: [mdx(), react()]
|
package/site/package.json
CHANGED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Fragment } from "react";
|
|
2
|
+
import { useReactCake } from "../../../../lib";
|
|
3
|
+
import styles from "./styles.module.css";
|
|
4
|
+
|
|
5
|
+
interface AxisProps {
|
|
6
|
+
position?: "top" | "bottom";
|
|
7
|
+
count?: number;
|
|
8
|
+
format?: (d: unknown) => string;
|
|
9
|
+
label?: string;
|
|
10
|
+
ticksValues?: number | DatumValue[] | ((ticks: unknown[]) => unknown[]);
|
|
11
|
+
showBaseline?: boolean;
|
|
12
|
+
grid?: boolean;
|
|
13
|
+
snapLabels?: boolean;
|
|
14
|
+
tickLength?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const AxisX = ({
|
|
18
|
+
ticks,
|
|
19
|
+
showBaseline = true,
|
|
20
|
+
position = "bottom",
|
|
21
|
+
grid = false,
|
|
22
|
+
format = (d) => String(d),
|
|
23
|
+
tickLength = 8,
|
|
24
|
+
}: AxisProps) => {
|
|
25
|
+
const { xScale } = useReactCake();
|
|
26
|
+
|
|
27
|
+
if (!xScale) return <></>;
|
|
28
|
+
|
|
29
|
+
const isBandwidth = typeof xScale.bandwidth === "function";
|
|
30
|
+
|
|
31
|
+
const tickVals = Array.isArray(ticks)
|
|
32
|
+
? ticks
|
|
33
|
+
: isBandwidth
|
|
34
|
+
? xScale.domain()
|
|
35
|
+
: typeof ticks === "function"
|
|
36
|
+
? ticks(xScale.ticks())
|
|
37
|
+
: xScale.ticks(ticks);
|
|
38
|
+
|
|
39
|
+
const halfBand = isBandwidth ? xScale.bandwidth() / 2 : 0;
|
|
40
|
+
|
|
41
|
+
const tickEls = tickVals.map((v) => {
|
|
42
|
+
const x = xScale(v);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Fragment key={`tick-${v.toString()}`}>
|
|
46
|
+
{grid && (
|
|
47
|
+
<div
|
|
48
|
+
className={styles.gridline}
|
|
49
|
+
style={{
|
|
50
|
+
top: 0,
|
|
51
|
+
bottom: 0,
|
|
52
|
+
left: `${x + halfBand}%`,
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
)}
|
|
56
|
+
<div
|
|
57
|
+
className={styles.tick}
|
|
58
|
+
style={{
|
|
59
|
+
left: `${x + halfBand}%`,
|
|
60
|
+
bottom: position === "bottom" ? -tickLength : "auto",
|
|
61
|
+
top: position === "top" ? -tickLength + 1 : "auto",
|
|
62
|
+
height: `${tickLength}px`,
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<span style={{ height: `${tickLength}px` }} className={styles.tick__mark}></span>
|
|
66
|
+
<span
|
|
67
|
+
className={styles.tick__text}
|
|
68
|
+
style={{
|
|
69
|
+
top: position === "bottom" ? `${tickLength}px` : "auto",
|
|
70
|
+
left: 0,
|
|
71
|
+
bottom: position === "top" ? `${tickLength + 2}px` : "auto",
|
|
72
|
+
transform: `translateX(-50%) ${position === "top" ? "translateY(-50%)" : ""}`,
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
{format(v)}
|
|
76
|
+
</span>
|
|
77
|
+
</div>
|
|
78
|
+
</Fragment>
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
className={styles.axis}
|
|
85
|
+
style={{
|
|
86
|
+
bottom: position === "bottom" ? 0 : "auto",
|
|
87
|
+
top: position === "top" ? 0 : "auto",
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{tickEls}
|
|
91
|
+
{showBaseline && (
|
|
92
|
+
<div
|
|
93
|
+
className={styles.baseline}
|
|
94
|
+
style={{
|
|
95
|
+
bottom: position === "bottom" ? 0 : "auto",
|
|
96
|
+
top: position === "top" ? 0 : "auto",
|
|
97
|
+
left: 0,
|
|
98
|
+
right: 0,
|
|
99
|
+
}}
|
|
100
|
+
></div>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export default AxisX;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
.axis {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
position: absolute;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.tick {
|
|
8
|
+
position: absolute;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.tick__mark {
|
|
12
|
+
border-left: 1px solid var(--blue-50);
|
|
13
|
+
position: absolute;
|
|
14
|
+
transform: translateX(-50%);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.gridline {
|
|
18
|
+
bottom: 0;
|
|
19
|
+
position: absolute;
|
|
20
|
+
border-left: 1px dotted var(--blue-50);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
.baseline {
|
|
25
|
+
position: absolute;
|
|
26
|
+
border-bottom: 1px solid var(--blue-50);
|
|
27
|
+
width: 100%;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.tick__text {
|
|
31
|
+
display: block;
|
|
32
|
+
font-size: var(--ms--1);
|
|
33
|
+
color: var(--blue-50);
|
|
34
|
+
position: relative;
|
|
35
|
+
white-space: nowrap;
|
|
36
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Fragment } from "react";
|
|
2
|
+
import { useReactCake } from "../../../../lib";
|
|
3
|
+
import styles from "./styles.module.css";
|
|
4
|
+
|
|
5
|
+
interface AxisYProps {
|
|
6
|
+
position?: "left" | "right";
|
|
7
|
+
format?: (d: unknown) => string;
|
|
8
|
+
label?: string;
|
|
9
|
+
ticks?: number | DatumValue[] | ((ticks: unknown[]) => unknown[]);
|
|
10
|
+
showBaseline?: boolean;
|
|
11
|
+
grid?: boolean;
|
|
12
|
+
showTicks?: boolean;
|
|
13
|
+
snapLabels?: boolean;
|
|
14
|
+
tickLength?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const AxisY = ({
|
|
18
|
+
ticks,
|
|
19
|
+
showBaseline = true,
|
|
20
|
+
grid = true,
|
|
21
|
+
showTicks = true,
|
|
22
|
+
tickLength = 8,
|
|
23
|
+
tickGutter = 3,
|
|
24
|
+
format = (d) => String(d),
|
|
25
|
+
}: AxisYProps) => {
|
|
26
|
+
const { yScale, padding } = useReactCake();
|
|
27
|
+
const isBandwidth = typeof yScale.bandwidth === "function";
|
|
28
|
+
|
|
29
|
+
const tickVals = Array.isArray(ticks)
|
|
30
|
+
? ticks
|
|
31
|
+
: isBandwidth
|
|
32
|
+
? yScale.domain()
|
|
33
|
+
: typeof ticks === "function"
|
|
34
|
+
? ticks(yScale.ticks())
|
|
35
|
+
: yScale.ticks(ticks);
|
|
36
|
+
|
|
37
|
+
const halfBand = isBandwidth ? yScale.bandwidth() / 2 : 0;
|
|
38
|
+
|
|
39
|
+
const tickEls = tickVals.map((v, i) => {
|
|
40
|
+
const y = yScale(v);
|
|
41
|
+
return (
|
|
42
|
+
<Fragment key={`tick-${i}`}>
|
|
43
|
+
{grid && y + halfBand > 0 && (
|
|
44
|
+
<div className={styles.gridline} style={{ bottom: `${y + halfBand}%`, left: 0 }}></div>
|
|
45
|
+
)}
|
|
46
|
+
{format(v) !== "" && (
|
|
47
|
+
<div
|
|
48
|
+
className={styles.tick}
|
|
49
|
+
style={{
|
|
50
|
+
width: `${tickLength}px`,
|
|
51
|
+
bottom: `${y + halfBand}%`,
|
|
52
|
+
left: -tickLength,
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
{showTicks && <span className={styles.mark}></span>}
|
|
56
|
+
<span
|
|
57
|
+
className={styles.text}
|
|
58
|
+
style={{ position: "absolute", right: `calc(100% + ${tickGutter}px)` }}
|
|
59
|
+
>
|
|
60
|
+
{format(v)}
|
|
61
|
+
</span>
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
</Fragment>
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className={styles.axis} style={{ bottom: 0, top: 0 }}>
|
|
70
|
+
{tickEls}
|
|
71
|
+
{showBaseline && <div className={styles.baseline} style={{ left: 0 }}></div>}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default AxisY;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
.axis {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
position: absolute;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.tick {
|
|
8
|
+
position: absolute;
|
|
9
|
+
border-top: 1px solid var(--blue-50);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
.gridline {
|
|
14
|
+
left: 0;
|
|
15
|
+
right: 0;
|
|
16
|
+
position: absolute;
|
|
17
|
+
border-top: 1px dotted var(--blue-50);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
.baseline {
|
|
22
|
+
position: absolute;
|
|
23
|
+
border-left: 1px solid var(--blue-50);
|
|
24
|
+
height: 100%;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.text {
|
|
28
|
+
display: block;
|
|
29
|
+
font-size: var(--ms--1);
|
|
30
|
+
color: var(--blue-50);
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
transform: translateY(-50%);
|
|
33
|
+
}
|
|
@@ -1,26 +1,43 @@
|
|
|
1
1
|
import { ReactCake, ScaledSvg, Html } from "../../../lib";
|
|
2
|
+
import AxisX from "./AxisX/AxisX";
|
|
3
|
+
import AxisY from "./AxisY";
|
|
2
4
|
import Columns from "./Columns";
|
|
3
|
-
import { scaleLinear, scaleBand } from "d3-scale"
|
|
5
|
+
import { scaleLinear, scaleBand } from "d3-scale";
|
|
6
|
+
import { max } from "d3-array";
|
|
7
|
+
import DebugPadding from "./DebugPadding";
|
|
4
8
|
|
|
5
9
|
const data = [
|
|
6
|
-
{year:
|
|
7
|
-
{year:
|
|
8
|
-
{year:
|
|
9
|
-
{year:
|
|
10
|
-
{year:
|
|
11
|
-
|
|
10
|
+
{ year: 2021, value: 18 },
|
|
11
|
+
{ year: 2022, value: 12 },
|
|
12
|
+
{ year: 2023, value: 22 },
|
|
13
|
+
{ year: 2024, value: 32 },
|
|
14
|
+
{ year: 2025, value: 15 },
|
|
15
|
+
{ year: 2026, value: 52 },
|
|
16
|
+
];
|
|
12
17
|
|
|
13
|
-
const xScale = scaleBand().paddingInner(.1)
|
|
18
|
+
const xScale = scaleBand().paddingInner(0.1).paddingOuter(0.2);
|
|
19
|
+
const yKey = "value";
|
|
14
20
|
|
|
15
21
|
export default function ColumnChart() {
|
|
16
22
|
return (
|
|
17
|
-
<ReactCake
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
<ReactCake
|
|
24
|
+
data={data}
|
|
25
|
+
x="year"
|
|
26
|
+
y={yKey}
|
|
27
|
+
yScale={scaleLinear()}
|
|
28
|
+
xScale={xScale}
|
|
29
|
+
percentRange={true}
|
|
30
|
+
yDomain={[0, max(data, (d) => d[yKey]) + 5]}
|
|
31
|
+
padding={{ top: 10, left: 35, right: 10, bottom: 30 }}
|
|
32
|
+
>
|
|
33
|
+
<Html overflow="visible">
|
|
34
|
+
<DebugPadding />
|
|
35
|
+
<AxisX grid ticks />
|
|
36
|
+
<AxisY grid ticks={3} />
|
|
37
|
+
</Html>
|
|
38
|
+
<ScaledSvg overflow="hidden">
|
|
39
|
+
<Columns />
|
|
40
|
+
</ScaledSvg>
|
|
41
|
+
</ReactCake>
|
|
42
|
+
);
|
|
26
43
|
}
|
|
@@ -1,30 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useReactCake } from "../../../lib";
|
|
2
2
|
|
|
3
3
|
export default function Columns() {
|
|
4
4
|
const { data, x, xGet, yGet, xScale } = useReactCake();
|
|
5
5
|
|
|
6
|
-
console.log(data)
|
|
7
|
-
|
|
8
6
|
return (
|
|
9
7
|
<g className="column-group">
|
|
10
8
|
{data?.map((d) => {
|
|
9
|
+
const valueY = yGet(d);
|
|
10
|
+
const xGot = xGet(d);
|
|
11
|
+
const xPos = Array.isArray(xGot) ? xGot[0] : xGot;
|
|
12
|
+
const colWidth = xScale.bandwidth ? xScale.bandwidth() : 20;
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
stroke="#196c9e"
|
|
24
|
-
fill="none"
|
|
25
|
-
/>
|
|
14
|
+
return (
|
|
15
|
+
<rect
|
|
16
|
+
key={x(d)}
|
|
17
|
+
x={xPos}
|
|
18
|
+
y={100 - valueY}
|
|
19
|
+
width={colWidth}
|
|
20
|
+
height={valueY}
|
|
21
|
+
stroke="var(--blue-50)"
|
|
22
|
+
fill="none"
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
26
25
|
})}
|
|
27
26
|
</g>
|
|
28
|
-
|
|
29
|
-
)
|
|
27
|
+
);
|
|
30
28
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useReactCake } from "../../../lib";
|
|
2
|
+
|
|
3
|
+
export default function DebugPadding() {
|
|
4
|
+
const { padding } = useReactCake();
|
|
5
|
+
return (
|
|
6
|
+
<div
|
|
7
|
+
style={{
|
|
8
|
+
position: "absolute",
|
|
9
|
+
left: 0,
|
|
10
|
+
right: 0,
|
|
11
|
+
top: 0,
|
|
12
|
+
bottom: 0,
|
|
13
|
+
boxShadow: `${padding.right}px 0 var(--blue-20), 0 ${padding.bottom}px var(--blue-20), 0 ${-padding.top}px var(--blue-20), -${padding.left}px 0 var(--blue-20)`,
|
|
14
|
+
}}
|
|
15
|
+
></div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { Context } from "../ReactCake/";
|
|
3
|
+
import { type ContextValue } from "../ReactCake/types";
|
|
4
|
+
|
|
5
|
+
export default function GanttChart({ currentStep = null, highlight, maxDate, maxLegalDate }) {
|
|
6
|
+
const { x2, xGet, yGet, data, xScale, yScale, zGet } = useContext<ContextValue>(Context);
|
|
7
|
+
|
|
8
|
+
const scaledData = data.map((d, i) => {
|
|
9
|
+
const _maxDate =
|
|
10
|
+
d.kind === "construction" && maxLegalDate ? Math.min(maxLegalDate, maxDate) : maxDate;
|
|
11
|
+
|
|
12
|
+
const _x2 = x2(d) > _maxDate ? _maxDate : x2(d);
|
|
13
|
+
|
|
14
|
+
const fromStep = d.from_step || i;
|
|
15
|
+
const isActive = currentStep >= fromStep;
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
...d,
|
|
19
|
+
width: isActive ? xScale(_x2) - xGet(d) : 0,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return scaledData.map((d, i) => {
|
|
24
|
+
const x1 = xGet(d);
|
|
25
|
+
const y = yGet(d);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<rect
|
|
29
|
+
key={`gantt-${i}`}
|
|
30
|
+
x={x1}
|
|
31
|
+
y={y}
|
|
32
|
+
width={d.width}
|
|
33
|
+
height={yScale.bandwidth()}
|
|
34
|
+
fill={zGet(d) || "lightgray"}
|
|
35
|
+
className={`gantt-block ${currentStep > -1 && highlight[currentStep].includes(i) ? "current" : ""}`}
|
|
36
|
+
></rect>
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { Context } from "../ReactCake/";
|
|
3
|
+
import { type ContextValue } from "../ReactCake/types";
|
|
4
|
+
|
|
5
|
+
interface OrdinalKeyProps {
|
|
6
|
+
shape?: "square" | "circle" | "line";
|
|
7
|
+
capitalise?: boolean;
|
|
8
|
+
format?: ((s: string | number) => string) | Record<string | number, string>;
|
|
9
|
+
align?: "start" | "center" | "end";
|
|
10
|
+
sort?: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function cap(s: string) {
|
|
14
|
+
return String(s).replace(/^\w/, (d) => d.toUpperCase());
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function OrdinalKey({
|
|
18
|
+
shape = "square",
|
|
19
|
+
align = "start",
|
|
20
|
+
sort = [],
|
|
21
|
+
format,
|
|
22
|
+
capitalise = true,
|
|
23
|
+
}: OrdinalKeyProps) {
|
|
24
|
+
const { zDomain, zScale } = useContext<ContextValue>(Context);
|
|
25
|
+
|
|
26
|
+
function displayName(val: string | number): string {
|
|
27
|
+
if (format) {
|
|
28
|
+
return typeof format === "function" ? format(val) : format[val] || val;
|
|
29
|
+
}
|
|
30
|
+
return capitalise === true ? cap(String(val)) : val;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="ordinal-key" style={{ justifyContent: align === "end" ? "flex-end" : align }}>
|
|
35
|
+
{zDomain
|
|
36
|
+
.sort((a, b) => sort.indexOf(b.kind) - sort.indexOf(a.kind))
|
|
37
|
+
.map((d) => {
|
|
38
|
+
return (
|
|
39
|
+
<div className="key-item" key={d}>
|
|
40
|
+
<div
|
|
41
|
+
className={`chip chip__${shape}`}
|
|
42
|
+
style={{
|
|
43
|
+
background:
|
|
44
|
+
shape === `line`
|
|
45
|
+
? `linear-gradient(-45deg, var(--color-pageFill) 40%, ${zScale(d)} 41%, ${zScale(d)} 59%, var(--color-pageFill) 60%)`
|
|
46
|
+
: zScale(d),
|
|
47
|
+
}}
|
|
48
|
+
></div>
|
|
49
|
+
<p className="name">{displayName(d)}</p>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
})}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
.ordinal-key {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-flow: column;
|
|
4
|
+
color: var(--o3-color-palette-black-80);
|
|
5
|
+
font-family: var(--o3-font-family-metric);
|
|
6
|
+
gap: 0.5em;
|
|
7
|
+
background: var(--o3-color-palette-paper);
|
|
8
|
+
box-shadow: 0 0 10px 10px var(--o3-color-palette-paper);
|
|
9
|
+
|
|
10
|
+
* {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.key-item {
|
|
16
|
+
display: flex;
|
|
17
|
+
gap: 0.1em;
|
|
18
|
+
align-items: center;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.chip {
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
position: relative;
|
|
24
|
+
width: 12px;
|
|
25
|
+
height: 12px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.chip__circle {
|
|
29
|
+
border-radius: 50%;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.chip__line:after {
|
|
33
|
+
content: "";
|
|
34
|
+
position: absolute;
|
|
35
|
+
border-width: 3px;
|
|
36
|
+
width: 14px;
|
|
37
|
+
transform: rotate(-45deg);
|
|
38
|
+
transform-origin: 14px 5px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.name {
|
|
42
|
+
display: inline-block;
|
|
43
|
+
font-size: var(--o3-font-size-negative-2);
|
|
44
|
+
margin-left: 0.2em;
|
|
45
|
+
line-height: 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
import "./code.css"
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
<!doctype html>
|
|
2
6
|
<html lang="en">
|
|
3
7
|
<head>
|
|
@@ -24,22 +28,38 @@
|
|
|
24
28
|
body {
|
|
25
29
|
margin: 0;
|
|
26
30
|
width: 100%;
|
|
27
|
-
padding:
|
|
28
|
-
font-family:
|
|
31
|
+
padding: .5rem;
|
|
32
|
+
font-family: Georgia, 'Times New Roman', Times, serif;
|
|
33
|
+
--blue-70: hsl(220, 0%, 20%);
|
|
34
|
+
--blue-50: hsl(220, 0%, 36%);
|
|
35
|
+
--blue-30: hsl(220, 0%, 76%);
|
|
36
|
+
--blue-20: hsl(220, 0%, 92%);
|
|
37
|
+
--blue-10: hsl(220, 0%, 97%);
|
|
38
|
+
--ms--1: .8rem
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
a, a:visited {
|
|
42
|
+
color: var(--blue-70);
|
|
29
43
|
}
|
|
30
44
|
|
|
31
45
|
.content {
|
|
46
|
+
border: 1px solid var(--blue-70);
|
|
32
47
|
max-width: 40rem;
|
|
33
48
|
line-height: 1.4;
|
|
34
|
-
|
|
49
|
+
padding: 1rem;
|
|
50
|
+
border-right: 1px solid var(--blue-70);
|
|
35
51
|
}
|
|
36
52
|
|
|
37
53
|
p, li {
|
|
38
54
|
letter-spacing: .002em;
|
|
39
55
|
}
|
|
40
56
|
|
|
57
|
+
h1 {
|
|
58
|
+
font-weight: 900;
|
|
59
|
+
}
|
|
60
|
+
|
|
41
61
|
h2 {
|
|
42
|
-
margin-top:
|
|
62
|
+
margin-top: 1rem;
|
|
43
63
|
margin-bottom: .25rem;
|
|
44
64
|
}
|
|
45
65
|
h3 {
|
|
@@ -50,20 +70,13 @@
|
|
|
50
70
|
|
|
51
71
|
p, ul, ol {
|
|
52
72
|
margin-bottom: 1rem;
|
|
73
|
+
&:last-child {
|
|
74
|
+
margin-bottom: 0;
|
|
75
|
+
}
|
|
53
76
|
}
|
|
54
77
|
|
|
55
78
|
ul, ol {
|
|
56
79
|
margin-left: 1em;
|
|
57
80
|
}
|
|
58
81
|
|
|
59
|
-
|
|
60
|
-
.astro-code {
|
|
61
|
-
border-top: 1px solid lightgray;
|
|
62
|
-
border-bottom: 1px solid lightgray;
|
|
63
|
-
margin: 1rem 0;
|
|
64
|
-
padding: 1em 0;
|
|
65
|
-
line-height: 1.25;
|
|
66
|
-
font-family: monospace;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
82
|
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
.astro-code {
|
|
2
|
+
--astro-code-background: hsl(51, 62.5%, 93.7%);
|
|
3
|
+
--astro-code-token-comment: #666;
|
|
4
|
+
margin: 1em 0;
|
|
5
|
+
margin-top: .5em;
|
|
6
|
+
padding: .5em;
|
|
7
|
+
font-size: 12px;
|
|
8
|
+
line-height: 1.25;
|
|
9
|
+
font-family: monospace;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
pre.astro-code .indent {
|
|
14
|
+
display: inline-block;
|
|
15
|
+
position: relative;
|
|
16
|
+
left: var(--indent-offset);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pre.astro-code .indent:empty {
|
|
20
|
+
height: 1lh;
|
|
21
|
+
vertical-align: bottom;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pre.astro-code .indent::before {
|
|
25
|
+
content: '';
|
|
26
|
+
position: absolute;
|
|
27
|
+
opacity: 0.5;
|
|
28
|
+
width: 1px;
|
|
29
|
+
height: 100%;
|
|
30
|
+
background-color: red;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pre.astro-code .line-number {
|
|
34
|
+
user-select: none;
|
|
35
|
+
color: #666;
|
|
36
|
+
margin-right: 2ch;
|
|
37
|
+
}
|
package/site/src/pages/index.mdx
CHANGED
|
@@ -17,13 +17,13 @@ A headless chart framework for React inspired by [Layercake](https://layercake.g
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install --save reactcake
|
|
20
|
+
npm install --save @awesomephant/reactcake
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## Usage
|
|
24
24
|
|
|
25
25
|
```tsx
|
|
26
|
-
import { ReactCake, Html, Svg } from "reactcake";
|
|
26
|
+
import { ReactCake, Html, Svg } from "@awesomephant/reactcake";
|
|
27
27
|
import { scaleLinear, scaleBand } from "d3-scale"
|
|
28
28
|
|
|
29
29
|
// Chart components live in your project
|
|
@@ -35,11 +35,11 @@ const data = [
|
|
|
35
35
|
{year: 2026, value: 52}
|
|
36
36
|
];
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
const yKey = "value";
|
|
38
|
+
// Use any d3 scale
|
|
40
39
|
const xScale = scaleTime();
|
|
41
40
|
const xScale = scaleBand();
|
|
42
41
|
|
|
42
|
+
// Build charts declaratively
|
|
43
43
|
return (
|
|
44
44
|
<ReactCake data={data} x="year" y="value" xScale={xScale} yScale={yScale}>
|
|
45
45
|
<Html>
|
|
@@ -63,7 +63,7 @@ ReactCake is built on [D3](https://d3js.org/what-is-d3).
|
|
|
63
63
|
|
|
64
64
|
### Relationship to Layercake
|
|
65
65
|
|
|
66
|
-
This project is an attempt to translate the overall approach and development ergonomics of the Layercake framework from Svelte to React. It does not aim for feature parity or identical API surfaces.
|
|
66
|
+
This project is an attempt to carefully translate the overall approach and development ergonomics of the Layercake framework from Svelte to React. It does not aim for feature parity or identical API surfaces.
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
### Prior work
|
package/site/tsconfig.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// TKTK
|