├── .gitignore ├── packages ├── headless-chart │ ├── .prettierrc │ ├── src │ │ ├── index.ts │ │ ├── vite-env.d.ts │ │ ├── charts │ │ │ ├── area-chart │ │ │ │ ├── default │ │ │ │ │ ├── y-axis-line.ts │ │ │ │ │ ├── x-axis-line.ts │ │ │ │ │ ├── title.ts │ │ │ │ │ ├── gridXLine.ts │ │ │ │ │ ├── gridYLine.ts │ │ │ │ │ ├── legend.ts │ │ │ │ │ ├── data-label.ts │ │ │ │ │ ├── x-axis-tick.ts │ │ │ │ │ ├── y-axis-tick.ts │ │ │ │ │ ├── x-axis-label.ts │ │ │ │ │ ├── y-axis-label.ts │ │ │ │ │ ├── plot.ts │ │ │ │ │ ├── x-axis.ts │ │ │ │ │ ├── y-axis.ts │ │ │ │ │ ├── getScale.ts │ │ │ │ │ ├── series.ts │ │ │ │ │ ├── grid.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── layout.ts │ │ │ │ │ └── area.ts │ │ │ │ ├── provider.ts │ │ │ │ ├── types.ts │ │ │ │ └── index.ts │ │ │ ├── bar-chart │ │ │ │ ├── default │ │ │ │ │ ├── y-axis-line.ts │ │ │ │ │ ├── x-axis-line.ts │ │ │ │ │ ├── title.ts │ │ │ │ │ ├── gridXLine.ts │ │ │ │ │ ├── gridYLine.ts │ │ │ │ │ ├── legend.ts │ │ │ │ │ ├── data-label.ts │ │ │ │ │ ├── x-axis-tick.ts │ │ │ │ │ ├── y-axis-tick.ts │ │ │ │ │ ├── x-axis-label.ts │ │ │ │ │ ├── y-axis-label.ts │ │ │ │ │ ├── plot.ts │ │ │ │ │ ├── getScale.ts │ │ │ │ │ ├── y-axis.ts │ │ │ │ │ ├── x-axis.ts │ │ │ │ │ ├── bar.ts │ │ │ │ │ ├── series.ts │ │ │ │ │ ├── grid.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── layout.ts │ │ │ │ │ └── bar-group.ts │ │ │ │ ├── provider.ts │ │ │ │ ├── types.ts │ │ │ │ └── index.ts │ │ │ ├── heatmap-chart │ │ │ │ ├── default │ │ │ │ │ ├── y-axis-line.ts │ │ │ │ │ ├── x-axis-line.ts │ │ │ │ │ ├── title.ts │ │ │ │ │ ├── x-axis-tick.ts │ │ │ │ │ ├── y-axis-tick.ts │ │ │ │ │ ├── x-axis-label.ts │ │ │ │ │ ├── y-axis-label.ts │ │ │ │ │ ├── x-axis.ts │ │ │ │ │ ├── y-axis.ts │ │ │ │ │ ├── get-scale.ts │ │ │ │ │ ├── plot.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── segment.ts │ │ │ │ │ ├── heatmap.ts │ │ │ │ │ └── layout.ts │ │ │ │ ├── provider.ts │ │ │ │ ├── types.ts │ │ │ │ └── index.ts │ │ │ ├── line-chart │ │ │ │ ├── default │ │ │ │ │ ├── y-axis-line.ts │ │ │ │ │ ├── x-axis-line.ts │ │ │ │ │ ├── title.ts │ │ │ │ │ ├── gridXLine.ts │ │ │ │ │ ├── gridYLine.ts │ │ │ │ │ ├── legend.ts │ │ │ │ │ ├── data-label.ts │ │ │ │ │ ├── x-axis-tick.ts │ │ │ │ │ ├── y-axis-tick.ts │ │ │ │ │ ├── x-axis-label.ts │ │ │ │ │ ├── y-axis-label.ts │ │ │ │ │ ├── plot.ts │ │ │ │ │ ├── x-axis.ts │ │ │ │ │ ├── y-axis.ts │ │ │ │ │ ├── getScale.ts │ │ │ │ │ ├── series.ts │ │ │ │ │ ├── grid.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── layout.ts │ │ │ │ │ └── line.ts │ │ │ │ ├── provider.ts │ │ │ │ ├── types.ts │ │ │ │ └── index.ts │ │ │ ├── bubble-chart │ │ │ │ ├── default │ │ │ │ │ ├── x-axis-line.ts │ │ │ │ │ ├── y-axis-line.ts │ │ │ │ │ ├── plot.ts │ │ │ │ │ ├── gridXLine.ts │ │ │ │ │ ├── gridYLine.ts │ │ │ │ │ ├── legend.ts │ │ │ │ │ ├── title.ts │ │ │ │ │ ├── data-label.ts │ │ │ │ │ ├── x-axis-tick.ts │ │ │ │ │ ├── y-axis-tick.ts │ │ │ │ │ ├── x-axis-label.ts │ │ │ │ │ ├── y-axis-label.ts │ │ │ │ │ ├── x-axis.ts │ │ │ │ │ ├── y-axis.ts │ │ │ │ │ ├── grid.ts │ │ │ │ │ ├── bubble.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── series.ts │ │ │ │ │ ├── layout.ts │ │ │ │ │ └── getScale.ts │ │ │ │ ├── provider.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── scatter-chart │ │ │ │ ├── default │ │ │ │ │ ├── x-axis-line.ts │ │ │ │ │ ├── y-axis-line.ts │ │ │ │ │ ├── plot.ts │ │ │ │ │ ├── gridXLine.ts │ │ │ │ │ ├── gridYLine.ts │ │ │ │ │ ├── title.ts │ │ │ │ │ ├── legend.ts │ │ │ │ │ ├── data-label.ts │ │ │ │ │ ├── x-axis-tick.ts │ │ │ │ │ ├── y-axis-tick.ts │ │ │ │ │ ├── x-axis-label.ts │ │ │ │ │ ├── x-axis.ts │ │ │ │ │ ├── y-axis-label.ts │ │ │ │ │ ├── y-axis.ts │ │ │ │ │ ├── grid.ts │ │ │ │ │ ├── scatter.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── series.ts │ │ │ │ │ ├── getScale.ts │ │ │ │ │ └── layout.ts │ │ │ │ ├── provider.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ └── index.ts │ │ ├── shared │ │ │ ├── cartesian │ │ │ │ ├── y-axis-line.ts │ │ │ │ ├── x-axis-line.ts │ │ │ │ ├── title.ts │ │ │ │ ├── data-label.ts │ │ │ │ ├── grid-x-line.ts │ │ │ │ ├── grid-y-line.ts │ │ │ │ ├── x-axis-tick.ts │ │ │ │ ├── y-axis-tick.ts │ │ │ │ ├── x-axis-label.ts │ │ │ │ ├── y-axis-label.ts │ │ │ │ ├── legend.ts │ │ │ │ ├── index.ts │ │ │ │ ├── grid.ts │ │ │ │ ├── layout.ts │ │ │ │ ├── plot.ts │ │ │ │ ├── x-axis.ts │ │ │ │ ├── types.ts │ │ │ │ ├── y-axis.ts │ │ │ │ └── getScale.ts │ │ │ └── utils │ │ │ │ └── scale.ts │ │ └── utils │ │ │ └── index.ts │ ├── .eslintrc.json │ ├── .gitignore │ ├── vite.config.ts │ ├── tsconfig.json │ ├── public │ │ └── vite.svg │ ├── package.json │ └── README.md └── docs │ ├── public │ ├── og.png │ ├── logo.png │ ├── discord.png │ ├── favicon.png │ ├── github-mark.png │ ├── meursyphus.jpeg │ ├── area-chart │ │ └── toast.png │ ├── bar-chart │ │ ├── nivo.png │ │ ├── toast.png │ │ ├── chartjs.png │ │ ├── echarts.png │ │ ├── rechart.png │ │ ├── highchart.png │ │ └── chartjs-radius.png │ ├── line-chart │ │ ├── nivo.png │ │ ├── toast.png │ │ ├── chartjs.png │ │ └── echarts.png │ ├── bubble-chart │ │ └── toast.png │ ├── github-mark-white.png │ ├── heatmap-chart │ │ └── toast.png │ └── scatter-chart │ │ └── toast.png │ ├── src │ ├── env.d.ts │ ├── layouts │ │ ├── HomeLayout.astro │ │ ├── ChartsLayout.astro │ │ ├── DocsLayout.astro │ │ └── Layout.astro │ ├── pages │ │ ├── i18n │ │ │ └── index.astro │ │ ├── 404.astro │ │ ├── robots.txt.ts │ │ ├── docs │ │ │ ├── utils.ts │ │ │ ├── SideBar.astro │ │ │ └── Toc.astro │ │ └── charts │ │ │ ├── TableOfContents.astro │ │ │ └── SandPack.tsx │ ├── i18n │ │ └── index.ts │ ├── content │ │ ├── config.ts │ │ ├── docs │ │ │ ├── ko │ │ │ │ ├── 01.getting-started │ │ │ │ │ ├── 01.introduction.mdx │ │ │ │ │ └── 02.installation.mdx │ │ │ │ └── 02.core-concepts │ │ │ │ │ └── 01.flitter-basics.mdx │ │ │ └── en │ │ │ │ └── 01.getting-started │ │ │ │ ├── 01.introduction.mdx │ │ │ │ └── 02.installation.mdx │ │ └── charts │ │ │ └── 01.bar │ │ │ └── 01.chartjs.mdx │ ├── middleware.ts │ └── components │ │ └── ui │ │ ├── Header.astro │ │ └── BarChart.tsx │ ├── svelte.config.js │ ├── .gitignore │ ├── tsconfig.json │ ├── .prettierrc.mjs │ ├── tailwind.config.mjs │ ├── package.json │ ├── astro.config.mjs │ └── README.md ├── image.png ├── .vscode └── settings.json ├── package.json ├── LICENSE ├── lunaria.config.json ├── README.md ├── docs-guide.md └── CLAUDE.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /packages/headless-chart/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /packages/headless-chart/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './charts' -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/image.png -------------------------------------------------------------------------------- /packages/headless-chart/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "highchart", 4 | "meursyphus", 5 | "nivo" 6 | ] 7 | } -------------------------------------------------------------------------------- /packages/docs/public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/og.png -------------------------------------------------------------------------------- /packages/docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/logo.png -------------------------------------------------------------------------------- /packages/docs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /packages/docs/public/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/discord.png -------------------------------------------------------------------------------- /packages/docs/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/favicon.png -------------------------------------------------------------------------------- /packages/docs/public/github-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/github-mark.png -------------------------------------------------------------------------------- /packages/docs/public/meursyphus.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/meursyphus.jpeg -------------------------------------------------------------------------------- /packages/docs/public/area-chart/toast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/area-chart/toast.png -------------------------------------------------------------------------------- /packages/docs/public/bar-chart/nivo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/bar-chart/nivo.png -------------------------------------------------------------------------------- /packages/docs/public/bar-chart/toast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/bar-chart/toast.png -------------------------------------------------------------------------------- /packages/docs/public/line-chart/nivo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/line-chart/nivo.png -------------------------------------------------------------------------------- /packages/docs/public/line-chart/toast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/line-chart/toast.png -------------------------------------------------------------------------------- /packages/docs/public/bar-chart/chartjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/bar-chart/chartjs.png -------------------------------------------------------------------------------- /packages/docs/public/bar-chart/echarts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/bar-chart/echarts.png -------------------------------------------------------------------------------- /packages/docs/public/bar-chart/rechart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/bar-chart/rechart.png -------------------------------------------------------------------------------- /packages/docs/public/bubble-chart/toast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/bubble-chart/toast.png -------------------------------------------------------------------------------- /packages/docs/public/github-mark-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/github-mark-white.png -------------------------------------------------------------------------------- /packages/docs/public/line-chart/chartjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/line-chart/chartjs.png -------------------------------------------------------------------------------- /packages/docs/public/line-chart/echarts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/line-chart/echarts.png -------------------------------------------------------------------------------- /packages/docs/public/bar-chart/highchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/bar-chart/highchart.png -------------------------------------------------------------------------------- /packages/docs/public/heatmap-chart/toast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/heatmap-chart/toast.png -------------------------------------------------------------------------------- /packages/docs/public/scatter-chart/toast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/scatter-chart/toast.png -------------------------------------------------------------------------------- /packages/docs/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from "@astrojs/svelte"; 2 | 3 | export default { 4 | preprocess: vitePreprocess(), 5 | }; 6 | -------------------------------------------------------------------------------- /packages/docs/public/bar-chart/chartjs-radius.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meursyphus/headless-chart/HEAD/packages/docs/public/bar-chart/chartjs-radius.png -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/y-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from '@shared/cartesian/index' 2 | 3 | export function YAxisLine() { 4 | return Cartesian.YAxisLine(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/y-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from '@shared/cartesian/index' 2 | 3 | export function YAxisLine() { 4 | return Cartesian.YAxisLine(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/y-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from '@shared/cartesian/index' 2 | 3 | export function YAxisLine() { 4 | return Cartesian.YAxisLine(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/y-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from '@shared/cartesian/index' 2 | 3 | export function YAxisLine() { 4 | return Cartesian.YAxisLine(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/x-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from '@shared/cartesian/index' 2 | 3 | export function XAxisLine() { 4 | return Cartesian.XAxisLine(); 5 | } 6 | 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/x-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from '@shared/cartesian/index' 2 | 3 | export function XAxisLine() { 4 | return Cartesian.XAxisLine(); 5 | } 6 | 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/x-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from "@shared/cartesian/index"; 2 | 3 | export function XAxisLine() { 4 | return Cartesian.XAxisLine(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/y-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from "@shared/cartesian/index"; 2 | 3 | export function YAxisLine() { 4 | return Cartesian.YAxisLine(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/x-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from '@shared/cartesian/index' 2 | 3 | export function XAxisLine() { 4 | return Cartesian.XAxisLine(); 5 | } 6 | 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/x-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from '@shared/cartesian/index' 2 | 3 | export function XAxisLine() { 4 | return Cartesian.XAxisLine(); 5 | } 6 | 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/x-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from "@shared/cartesian/index"; 2 | 3 | export function XAxisLine() { 4 | return Cartesian.XAxisLine(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/y-axis-line.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from "@shared/cartesian/index"; 2 | 3 | export function YAxisLine() { 4 | return Cartesian.YAxisLine(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/docs/src/layouts/HomeLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "./Layout.astro"; 3 | interface Props { 4 | title?: string; 5 | } 6 | 7 | const { title } = Astro.props; 8 | --- 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/docs/src/pages/i18n/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../../layouts/Layout.astro"; 3 | import LunariaHtml from "../../../public/lunaria.html?raw"; 4 | --- 5 | 6 | 7 |
8 | 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/y-axis-line.ts: -------------------------------------------------------------------------------- 1 | import { Container, type Widget } from '@meursyphus/flitter'; 2 | 3 | export function YAxisLine(): Widget { 4 | return Container({ 5 | color: 'black', 6 | width: 1, 7 | height: Infinity 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/x-axis-line.ts: -------------------------------------------------------------------------------- 1 | import { Container, type Widget } from "@meursyphus/flitter"; 2 | 3 | export function XAxisLine(): Widget { 4 | return Container({ 5 | color: 'black', 6 | height: 1, 7 | width: Infinity 8 | }); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/title.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from '../types'; 2 | import * as Cartesian from '@shared/cartesian/index' 3 | 4 | export function Title(...args: Parameters) { 5 | return Cartesian.Title(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/title.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianCustom } from "./types"; 2 | import { Text, type Widget } from "@meursyphus/flitter"; 3 | 4 | export function Title({ name }: Parameters[0]): Widget { 5 | return Text(name); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/title.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function Title(...args: Parameters) { 5 | return Cartesian.Title(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/gridXLine.ts: -------------------------------------------------------------------------------- 1 | import { BarChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function GridXLine(..._: Parameters) { 5 | return Cartesian.GridXLine(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/gridYLine.ts: -------------------------------------------------------------------------------- 1 | import { BarChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function GridYLine(..._: Parameters) { 5 | return Cartesian.GridYLine(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/legend.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from '../types'; 2 | import * as Cartesian from '@shared/cartesian/index' 3 | 4 | export function Legend(...args: Parameters) { 5 | return Cartesian.Legend(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/title.ts: -------------------------------------------------------------------------------- 1 | import type { HeatmapCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function Title(...args: Parameters) { 5 | return Cartesian.Title(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/title.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function Title(...args: Parameters) { 5 | return Cartesian.Title(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/gridXLine.ts: -------------------------------------------------------------------------------- 1 | import { AreaChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function GridXLine(..._: Parameters) { 5 | return Cartesian.GridXLine(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/gridYLine.ts: -------------------------------------------------------------------------------- 1 | import { AreaChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function GridYLine(..._: Parameters) { 5 | return Cartesian.GridYLine(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/legend.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function Legend(...args: Parameters) { 5 | return Cartesian.Legend(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/plot.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function Plot(...args: Parameters) { 5 | return Cartesian.Plot(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/gridXLine.ts: -------------------------------------------------------------------------------- 1 | import { LineChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function GridXLine(..._: Parameters) { 5 | return Cartesian.GridXLine(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/gridYLine.ts: -------------------------------------------------------------------------------- 1 | import { LineChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function GridYLine(..._: Parameters) { 5 | return Cartesian.GridYLine(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/legend.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function Legend(...args: Parameters) { 5 | return Cartesian.Legend(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/plot.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function Plot(...args: Parameters) { 5 | return Cartesian.Plot(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "plugin:prettier/recommended"], 3 | "parserOptions": { 4 | "ecmaVersion": 2020, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "browser": true, 9 | "es2020": true 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/gridXLine.ts: -------------------------------------------------------------------------------- 1 | import { BubbleChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function GridXLine(..._: Parameters) { 5 | return Cartesian.GridXLine(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/gridYLine.ts: -------------------------------------------------------------------------------- 1 | import { BubbleChartCustom} from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function GridYLine(..._: Parameters) { 5 | return Cartesian.GridYLine(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/legend.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function Legend(...args: Parameters) { 5 | return Cartesian.Legend(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/title.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function Title(...args: Parameters) { 5 | return Cartesian.Title(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/gridXLine.ts: -------------------------------------------------------------------------------- 1 | import { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function GridXLine(..._: Parameters) { 5 | return Cartesian.GridXLine(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/gridYLine.ts: -------------------------------------------------------------------------------- 1 | import { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function GridYLine(..._: Parameters) { 5 | return Cartesian.GridYLine(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/title.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function Title(...args: Parameters) { 5 | return Cartesian.Title(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/data-label.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from '../types'; 2 | import * as Cartesian from '@shared/cartesian/index' 3 | 4 | export function DataLabel(...args: Parameters) { 5 | return Cartesian.DataLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/x-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from '../types'; 2 | import * as Cartesian from '@shared/cartesian/index' 3 | 4 | export function XAxisTick(...args: Parameters) { 5 | return Cartesian.XAxisTick(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/y-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from '../types'; 2 | import * as Cartesian from '@shared/cartesian/index' 3 | 4 | export function YAxisTick(...args: Parameters) { 5 | return Cartesian.YAxisTick(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/legend.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function Legend(...args: Parameters) { 5 | return Cartesian.Legend(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/data-label.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianCustom } from "./types"; 2 | import { SizedBox, type Widget } from "@meursyphus/flitter"; 3 | 4 | export function DataLabel(config: Parameters[0]): Widget { 5 | return SizedBox.shrink(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/docs/src/pages/404.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../layouts/HomeLayout.astro"; 3 | --- 4 | 5 | 6 |
7 |

404

8 |

죄송합니다. 찾으시는 페이지를 찾을 수 없습니다.

9 | 홈으로 돌아가기 10 |
11 |
-------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/data-label.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function DataLabel(...args: Parameters) { 5 | return Cartesian.DataLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/x-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxisTick(...args: Parameters) { 5 | return Cartesian.XAxisTick(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/y-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxisTick(...args: Parameters) { 5 | return Cartesian.YAxisTick(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/x-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from '../types'; 2 | import * as Cartesian from '@shared/cartesian/index' 3 | 4 | export function XAxisLabel(...args: Parameters) { 5 | return Cartesian.XAxisLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/y-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from '../types'; 2 | import * as Cartesian from '@shared/cartesian/index' 3 | 4 | export function YAxisLabel(...args: Parameters) { 5 | return Cartesian.YAxisLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/x-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { HeatmapCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxisTick(...args: Parameters) { 5 | return Cartesian.XAxisTick(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/y-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { HeatmapCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxisTick(...args: Parameters) { 5 | return Cartesian.YAxisTick(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/data-label.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function DataLabel(...args: Parameters) { 5 | return Cartesian.DataLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/x-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxisTick(...args: Parameters) { 5 | return Cartesian.XAxisTick(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/y-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxisTick(...args: Parameters) { 5 | return Cartesian.YAxisTick(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/x-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxisLabel(...args: Parameters) { 5 | return Cartesian.XAxisLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/y-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxisLabel(...args: Parameters) { 5 | return Cartesian.YAxisLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/data-label.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function DataLabel(...args: Parameters) { 5 | return Cartesian.DataLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/x-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxisTick(...args: Parameters) { 5 | return Cartesian.XAxisTick(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/y-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxisTick(...args: Parameters) { 5 | return Cartesian.YAxisTick(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/x-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { HeatmapCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxisLabel(...args: Parameters) { 5 | return Cartesian.XAxisLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/y-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { HeatmapCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxisLabel(...args: Parameters) { 5 | return Cartesian.YAxisLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/x-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxisLabel(...args: Parameters) { 5 | return Cartesian.XAxisLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/y-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxisLabel(...args: Parameters) { 5 | return Cartesian.YAxisLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/plot.ts: -------------------------------------------------------------------------------- 1 | //xAxis, yAxis, series, 2 | 3 | import type { AreaChartCustom } from "../types"; 4 | import * as Cartesian from "@shared/cartesian/index"; 5 | 6 | export function Plot(...args: Parameters) { 7 | return Cartesian.Plot(args[0]); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/plot.ts: -------------------------------------------------------------------------------- 1 | //xAxis, yAxis, series, 2 | 3 | import type { BarChartCustom } from "../types"; 4 | import * as Cartesian from "@shared/cartesian/index"; 5 | 6 | export function Plot(...args: Parameters) { 7 | return Cartesian.Plot(args[0]); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/x-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxisLabel(...args: Parameters) { 5 | return Cartesian.XAxisLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/y-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxisLabel(...args: Parameters) { 5 | return Cartesian.YAxisLabel(args[0]); 6 | } 7 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/plot.ts: -------------------------------------------------------------------------------- 1 | //xAxis, yAxis, series, 2 | 3 | import type { LineChartCustom } from "../types"; 4 | import * as Cartesian from "@shared/cartesian/index"; 5 | 6 | export function Plot(...args: Parameters) { 7 | return Cartesian.Plot(args[0]); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/grid-x-line.ts: -------------------------------------------------------------------------------- 1 | import { Container, type Widget } from "@meursyphus/flitter"; 2 | 3 | export function GridXLine({ color = "black" }: { color?: string } = {}): Widget { 4 | return Container({ 5 | width: Infinity, 6 | height: 1, 7 | color, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/grid-y-line.ts: -------------------------------------------------------------------------------- 1 | import { Container, type Widget } from "@meursyphus/flitter"; 2 | 3 | export function GridYLine({ color = "black" }: { color?: string } = {}): Widget { 4 | return Container({ 5 | width: 1, 6 | height: Infinity, 7 | color, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/x-axis.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxis(...args: Parameters) { 5 | return Cartesian.XAxis(args[0], { 6 | type: "value", 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/y-axis.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxis(...args: Parameters) { 5 | return Cartesian.YAxis(args[0], { 6 | type: "value", 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/x-axis.ts: -------------------------------------------------------------------------------- 1 | import type { HeatmapCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxis(...args: Parameters) { 5 | return Cartesian.XAxis(args[0], { 6 | type: "label", 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/y-axis.ts: -------------------------------------------------------------------------------- 1 | import type { HeatmapCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxis(...args: Parameters) { 5 | return Cartesian.YAxis(args[0], { 6 | type: "label", 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/x-axis.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxis(...args: Parameters) { 5 | return Cartesian.XAxis(args[0], { 6 | type: "value", 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/y-axis.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxis(...args: Parameters) { 5 | return Cartesian.YAxis(args[0], { 6 | type: "value", 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/data-label.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function DataLabel( 5 | ...args: Parameters 6 | ) { 7 | return Cartesian.DataLabel(args[0]); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/x-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxisTick( 5 | ...args: Parameters 6 | ) { 7 | return Cartesian.XAxisTick(args[0]); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/y-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxisTick( 5 | ...args: Parameters 6 | ) { 7 | return Cartesian.YAxisTick(args[0]); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/x-axis.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxis(...args: Parameters) { 5 | return Cartesian.XAxis(args[0], { 6 | type: "value", 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/y-axis.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxis(...args: Parameters) { 5 | return Cartesian.YAxis(args[0], { 6 | type: "value", 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/x-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxisLabel( 5 | ...args: Parameters 6 | ) { 7 | return Cartesian.XAxisLabel(args[0]); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/x-axis.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxis(...args: Parameters) { 5 | return Cartesian.XAxis(args[0], { 6 | type: "value", 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/y-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxisLabel( 5 | ...args: Parameters 6 | ) { 7 | return Cartesian.YAxisLabel(args[0]); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/y-axis.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxis(...args: Parameters) { 5 | return Cartesian.YAxis(args[0], { 6 | type: "value", 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/docs/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/getScale.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartData, BarChartScale } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function getScale({ 5 | datasets, 6 | }: Omit): BarChartScale { 7 | return Cartesian.getScale({ 8 | datasets, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/get-scale.ts: -------------------------------------------------------------------------------- 1 | import type { HeatmapData, HeatmapScale } from "../types"; 2 | 3 | export function getScale({ 4 | values, 5 | }: Omit): HeatmapScale { 6 | return { 7 | min: Math.min(...values.flat()), 8 | max: Math.max(...values.flat()), 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/getScale.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartData, AreaChartScale } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function getScale({ 5 | datasets, 6 | }: Omit): AreaChartScale { 7 | return Cartesian.getScale({ 8 | datasets, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/getScale.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartData, LineChartScale } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function getScale({ 5 | datasets, 6 | }: Omit): LineChartScale { 7 | return Cartesian.getScale({ 8 | datasets, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/x-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianCustom } from "./types"; 2 | import { Container, type Widget } from "@meursyphus/flitter"; 3 | 4 | export function XAxisTick(_: Parameters[0]): Widget { 5 | return Container({ 6 | width: 1, 7 | height: 4, 8 | color: "black", 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/y-axis-tick.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianCustom } from "./types"; 2 | import { Container, type Widget } from "@meursyphus/flitter"; 3 | 4 | export function YAxisTick(_: Parameters[0]): Widget { 5 | return Container({ 6 | width: 4, 7 | height: 1, 8 | color: "black", 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "jsxImportSource": "react", 6 | "baseUrl": ".", 7 | "paths": { 8 | "$components/*": ["./src/components/*"], 9 | "$layouts/*": ["./src/layouts/*"], 10 | "$utils/*": ["./src/utils/*"] 11 | } 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/x-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianCustom } from "./types"; 2 | import { Text, TextStyle, type Widget } from "@meursyphus/flitter"; 3 | 4 | export function XAxisLabel({ 5 | name, 6 | }: Parameters[0]): Widget { 7 | return Text(name, { style: new TextStyle({ fontSize: 12, color: "black" }) }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/y-axis-label.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianCustom } from "./types"; 2 | import { Text, TextStyle, type Widget } from "@meursyphus/flitter"; 3 | 4 | export function YAxisLabel({ 5 | name, 6 | }: Parameters[0]): Widget { 7 | return Text(name, { style: new TextStyle({ fontSize: 12, color: "black" }) }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/docs/.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | export default { 3 | plugins: [ 4 | "prettier-plugin-astro", 5 | "prettier-plugin-svelte", 6 | "prettier-plugin-tailwindcss", 7 | ], 8 | overrides: [ 9 | { 10 | files: "*.astro", 11 | options: { 12 | parser: "astro", 13 | }, 14 | }, 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /packages/docs/src/pages/robots.txt.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from 'astro'; 2 | 3 | const getRobotsTxt = (sitemapURL: URL) => ` 4 | User-agent: * 5 | Allow: / 6 | 7 | Sitemap: ${sitemapURL.href} 8 | `; 9 | 10 | export const GET: APIRoute = ({ site }) => { 11 | const sitemapURL = new URL('sitemap-index.xml', site); 12 | return new Response(getRobotsTxt(sitemapURL)); 13 | }; -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/y-axis.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function YAxis(...args: Parameters) { 5 | const { direction } = args[1]; 6 | return Cartesian.YAxis(args[0], { 7 | type: direction === "horizontal" ? "label" : "value", 8 | }); 9 | } -------------------------------------------------------------------------------- /packages/headless-chart/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/x-axis.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from "../types"; 2 | import * as Cartesian from "@shared/cartesian/index"; 3 | 4 | export function XAxis(...args: Parameters) { 5 | const { direction } = args[1]; 6 | return Cartesian.XAxis(args[0], { 7 | type: direction === "vertical" ? "label" : "value", 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/docs/tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | import typography from "@tailwindcss/typography"; 2 | 3 | export default { 4 | content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], 5 | theme: { 6 | extend: { 7 | typography: ({ theme }) => ({ 8 | modern: { 9 | css: { 10 | }, 11 | }, 12 | }), 13 | }, 14 | }, 15 | plugins: [typography], 16 | }; 17 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/bar.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from '../types'; 2 | import { Container } from '@meursyphus/flitter'; 3 | 4 | export function Bar(...[_, { direction }]: Parameters) { 5 | return Container({ 6 | width: direction === 'vertical' ? 5 : undefined, 7 | height: direction === 'horizontal' ? 5 : 100, 8 | color: 'black' 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/series.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom } from "../types"; 2 | import { Stack, Positioned } from "@meursyphus/flitter"; 3 | 4 | export function Series(...[{ lines }]: Parameters) { 5 | return Stack({ 6 | children: lines.map((line) => 7 | Positioned.fill({ 8 | child: line, 9 | }), 10 | ), 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/docs/src/layouts/ChartsLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "./Layout.astro"; 3 | interface Props { 4 | title?: string; 5 | description?: string; 6 | image?: string; 7 | } 8 | 9 | const { title, description, image } = Astro.props; 10 | --- 11 | 12 | 13 | 14 | 15 | 16 | 20 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/series.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom } from "../types"; 2 | import { Stack, Positioned } from "@meursyphus/flitter"; 3 | 4 | export function Series( 5 | ...[{ areas: lines }]: Parameters 6 | ) { 7 | return Stack({ 8 | children: lines.map((line) => 9 | Positioned.fill({ 10 | child: line, 11 | }), 12 | ), 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/plot.ts: -------------------------------------------------------------------------------- 1 | import { SizedBox } from "@meursyphus/flitter"; 2 | import type { HeatmapCustom } from "../types"; 3 | import * as Cartesian from "@shared/cartesian/index"; 4 | 5 | export function Plot(...[{ xAxis, yAxis, heatmap }]: Parameters) { 6 | return Cartesian.Plot({ 7 | xAxis, 8 | yAxis, 9 | series: heatmap, 10 | grid: SizedBox.shrink(), 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/docs/src/layouts/DocsLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "./Layout.astro"; 3 | interface Props { 4 | title?: string; 5 | description?: string; 6 | image?: string; 7 | } 8 | 9 | const { title, description, image } = Astro.props; 10 | --- 11 | 12 | 13 | 14 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /packages/docs/src/pages/docs/utils.ts: -------------------------------------------------------------------------------- 1 | export function normalizePath(path: string) { 2 | return path.endsWith("/") ? path.slice(0, -1) : path; 3 | } 4 | const NAV_GROUP: Record = { 5 | "Basic Widgets": 3, 6 | "Core Concepts": 2, 7 | "Interactions and Animations": 5, 8 | Layout: 4, 9 | Widgets: 999, 10 | "Getting Started": 1, 11 | "Advanced Features": 6, 12 | }; 13 | 14 | export function getNavOrder(navGroup: string) { 15 | return NAV_GROUP[navGroup] ?? 9999; 16 | } -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/legend.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianCustom } from "./types"; 2 | import { EdgeInsets, Padding, Text, TextStyle, type Widget } from "@meursyphus/flitter"; 3 | 4 | export function Legend(...[{ name }]: Parameters): Widget { 5 | return Padding({ 6 | padding: EdgeInsets.only({ right: 5 }), 7 | child: Text(name, { 8 | style: new TextStyle({ 9 | fontSize: 12, 10 | }), 11 | }), 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/grid.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from "@shared/cartesian/index"; 2 | import { BubbleChartCustom } from "../types"; 3 | 4 | export function Grid( 5 | ...[{ xLine, yLine }, { scale }]: Parameters 6 | ) { 7 | const x = (scale.x.max - scale.x.min)/ scale.x.step; 8 | const y = (scale.y.max - scale.y.min)/ scale.y.step; 9 | 10 | return Cartesian.Grid({ 11 | xLine, 12 | yLine, 13 | x, 14 | y, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./get-scale"; 2 | export * from "./layout"; 3 | export * from "./title"; 4 | export * from "./x-axis-label"; 5 | export * from "./x-axis-tick"; 6 | export * from "./x-axis"; 7 | export * from "./y-axis-label"; 8 | export * from "./y-axis-tick"; 9 | export * from "./y-axis"; 10 | export * from "./x-axis-line"; 11 | export * from "./y-axis-line"; 12 | export * from './plot' 13 | export * from './heatmap' 14 | export * from './segment' 15 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/grid.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from "@shared/cartesian/index"; 2 | import { ScatterChartCustom } from "../types"; 3 | 4 | export function Grid( 5 | ...[{ xLine, yLine }, { scale }]: Parameters 6 | ) { 7 | const x = (scale.x.max - scale.x.min) / scale.x.step; 8 | const y = (scale.y.max - scale.y.min) / scale.y.step; 9 | 10 | return Cartesian.Grid({ 11 | xLine, 12 | yLine, 13 | x, 14 | y, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/grid.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from "@shared/cartesian/index"; 2 | import { AreaChartCustom } from "../types"; 3 | 4 | export function Grid( 5 | ...[{ xLine, yLine }, { data, scale }]: Parameters 6 | ) { 7 | const labelCount = data.labels.length - 1; 8 | const valueCount = (scale.max - scale.min) / scale.step; 9 | 10 | return Cartesian.Grid({ 11 | xLine, 12 | yLine, 13 | y: valueCount, 14 | x: labelCount, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/grid.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from "@shared/cartesian/index"; 2 | import { LineChartCustom } from "../types"; 3 | 4 | export function Grid( 5 | ...[{ xLine, yLine }, { data, scale }]: Parameters 6 | ) { 7 | const labelCount = data.labels.length - 1; 8 | const valueCount = (scale.max - scale.min) / scale.step; 9 | 10 | return Cartesian.Grid({ 11 | xLine, 12 | yLine, 13 | y: valueCount, 14 | x: labelCount, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/scatter.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import { Container, BoxDecoration } from "@meursyphus/flitter"; 3 | 4 | export function Scatter( 5 | ...[{ }, { scale }]: Parameters< 6 | ScatterChartCustom["scatter"] 7 | > 8 | ) { 9 | const radius = 10 10 | 11 | return Container({ 12 | width: 10, 13 | height: 10, 14 | decoration: new BoxDecoration({ 15 | color: "black", 16 | shape: "circle", 17 | }), 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/segment.ts: -------------------------------------------------------------------------------- 1 | import { Container, Opacity } from "@meursyphus/flitter"; 2 | import type { HeatmapCustom } from "../types"; 3 | 4 | export function Segment( 5 | ...[{ value, xIndex, yIndex }, { scale }]: Parameters< 6 | HeatmapCustom["segment"] 7 | > 8 | ) { 9 | return Opacity({ 10 | opacity: (value - scale.min) / (scale.max - scale.min), 11 | child: Container({ 12 | width: 100, 13 | height: 100, 14 | color: "black", 15 | }), 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /packages/docs/src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number]; 2 | 3 | export const DEFAULT_LANGUAGE = "en"; 4 | 5 | export const LANGUAGE_LIST = [ 6 | { 7 | title: "English", 8 | locale: "en", 9 | }, 10 | { 11 | title: "한국어", 12 | locale: "ko", 13 | }, 14 | { 15 | title: "简体中文", 16 | locale: "cn", 17 | }, 18 | { 19 | title: "日本語", 20 | locale: "ja", 21 | }, 22 | ]; 23 | 24 | export const SUPPORTED_LANGUAGES = LANGUAGE_LIST.map( 25 | (language) => language.locale, 26 | ); 27 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getScale' 2 | export * from './data-label' 3 | export * from './layout' 4 | export * from './legend' 5 | export * from './plot' 6 | export * from './title' 7 | export * from './x-axis-label' 8 | export * from './x-axis-tick' 9 | export * from './x-axis' 10 | export * from './y-axis-label' 11 | export * from './y-axis-tick' 12 | export * from './y-axis' 13 | export * from './x-axis-line' 14 | export * from './y-axis-line' 15 | export * from './grid' 16 | export * from './grid-x-line' 17 | export * from './grid-y-line' 18 | 19 | -------------------------------------------------------------------------------- /packages/headless-chart/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import tsconfigPaths from "vite-tsconfig-paths"; 3 | import dts from "vite-plugin-dts"; 4 | 5 | export default defineConfig({ 6 | plugins: [tsconfigPaths(), dts()], 7 | build: { 8 | lib: { 9 | entry: "src/index.ts", 10 | name: "@meursyphus/headless-chart", 11 | fileName: (format) => `headless-chart.${format}.js`, 12 | }, 13 | rollupOptions: { 14 | external: ["@meursyphus/flitter"], 15 | output: { 16 | globals: {}, 17 | }, 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/series.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from '../types'; 2 | import { Container, Flexible, Flex, Axis } from '@meursyphus/flitter'; 3 | 4 | export function Series(...[{ barGroups }, { direction }]: Parameters) { 5 | return Container({ 6 | height: Infinity, 7 | width: Infinity, 8 | child: Flex({ 9 | direction: direction === 'vertical' ? Axis.horizontal : Axis.vertical, 10 | children: barGroups.map((barGroup) => 11 | Flexible({ 12 | flex: 1, 13 | child: barGroup 14 | }) 15 | ) 16 | }) 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/provider.ts: -------------------------------------------------------------------------------- 1 | import { type Widget, Provider, BuildContext } from '@meursyphus/flitter'; 2 | import type { BarChartConfig } from './types'; 3 | 4 | const BAR_CHART_CONTEXT_KEY = Symbol('BarChartKey'); 5 | 6 | export function BarChartConfigProvider({ child, value }: { child: Widget; value: BarChartConfig }): Widget { 7 | return Provider({ 8 | child, 9 | providerKey: BAR_CHART_CONTEXT_KEY, 10 | value 11 | }); 12 | } 13 | 14 | BarChartConfigProvider.of = (context: BuildContext): BarChartConfig => { 15 | return Provider.of(BAR_CHART_CONTEXT_KEY, context); 16 | }; 17 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/provider.ts: -------------------------------------------------------------------------------- 1 | import { type Widget, Provider, BuildContext } from '@meursyphus/flitter'; 2 | import type { LineChartConfig } from './types'; 3 | 4 | const BAR_CHART_CONTEXT_KEY = Symbol('BarChartKey'); 5 | 6 | export function LineChartConfigProvider({ child, value }: { child: Widget; value: LineChartConfig }): Widget { 7 | return Provider({ 8 | child, 9 | providerKey: BAR_CHART_CONTEXT_KEY, 10 | value 11 | }); 12 | } 13 | 14 | LineChartConfigProvider.of = (context: BuildContext): LineChartConfig => { 15 | return Provider.of(BAR_CHART_CONTEXT_KEY, context); 16 | }; 17 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/grid.ts: -------------------------------------------------------------------------------- 1 | import * as Cartesian from "@shared/cartesian/index"; 2 | import { BarChartCustom } from "../types"; 3 | 4 | export function Grid( 5 | ...[{ xLine, yLine }, { data, direction, scale }]: Parameters< 6 | BarChartCustom["grid"] 7 | > 8 | ) { 9 | const labelCount = data.labels.length; 10 | const valueCount = (scale.max - scale.min) / scale.step; 11 | 12 | return Cartesian.Grid({ 13 | xLine, 14 | yLine, 15 | x: direction === "vertical" ? labelCount : valueCount, 16 | y: direction === "horizontal" ? labelCount : valueCount, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/heatmap.ts: -------------------------------------------------------------------------------- 1 | import { Column, Flexible, Row } from "@meursyphus/flitter"; 2 | import type { HeatmapCustom } from "../types"; 3 | 4 | export function Heatmap( 5 | ...[{ segments },]: Parameters 6 | ) { 7 | const rows = segments.map((row) => { 8 | return Row({ 9 | children: row.map((segment) => 10 | Flexible({ 11 | flex: 1, 12 | child: segment, 13 | }), 14 | ), 15 | }); 16 | }); 17 | 18 | return Column({ 19 | children: rows.map((row) => Flexible({ flex: 1, child: row })), 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BarChart } from "./bar-chart"; 2 | export { default as LineChart } from "./line-chart"; 3 | export { default as BubbleChart } from "./bubble-chart"; 4 | export { default as AreaChart } from "./area-chart"; 5 | export { default as ScatterChart } from "./scatter-chart"; 6 | export { default as HeatmapChart } from "./heatmap-chart"; 7 | export * from "./bar-chart/types"; 8 | export * from "./line-chart/types"; 9 | export * from "./bubble-chart/types"; 10 | export * from "./area-chart/types"; 11 | export * from "./scatter-chart/types"; 12 | export * from "./heatmap-chart/types"; 13 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/bubble.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import { Container, BoxDecoration } from "@meursyphus/flitter"; 3 | 4 | export function Bubble( 5 | ...[{ value, label, legend, index }, { scale }]: Parameters 6 | ) { 7 | const normValue = (value - scale.value.min) / (scale.value.max - scale.value.min); 8 | const radius = 5 + normValue * 20; 9 | 10 | return Container({ 11 | width: radius * 2, 12 | height: radius * 2, 13 | decoration: new BoxDecoration({ 14 | color: "black", 15 | shape: "circle", 16 | }), 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./getScale"; 2 | export * from "./area"; 3 | export * from "./data-label"; 4 | export * from "./layout"; 5 | export * from "./legend"; 6 | export * from "./plot"; 7 | export * from "./series"; 8 | export * from "./title"; 9 | export * from "./x-axis-label"; 10 | export * from "./x-axis-tick"; 11 | export * from "./x-axis"; 12 | export * from "./y-axis-label"; 13 | export * from "./y-axis-tick"; 14 | export * from "./y-axis"; 15 | export * from "./x-axis-line"; 16 | export * from "./y-axis-line"; 17 | export * from "./grid"; 18 | export * from "./gridXLine"; 19 | export * from "./gridYLine"; 20 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/provider.ts: -------------------------------------------------------------------------------- 1 | import { type Widget, Provider, BuildContext } from "@meursyphus/flitter"; 2 | import type { AreaChartConfig } from "./types"; 3 | 4 | const AREA_CHART_CONTEXT_KEY = Symbol("BarChartKey"); 5 | 6 | export function AreaChartConfigProvider({ 7 | child, 8 | value, 9 | }: { 10 | child: Widget; 11 | value: AreaChartConfig; 12 | }): Widget { 13 | return Provider({ 14 | child, 15 | providerKey: AREA_CHART_CONTEXT_KEY, 16 | value, 17 | }); 18 | } 19 | 20 | AreaChartConfigProvider.of = (context: BuildContext): AreaChartConfig => { 21 | return Provider.of(AREA_CHART_CONTEXT_KEY, context); 22 | }; 23 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./getScale"; 2 | export * from "./bubble"; 3 | export * from "./data-label"; 4 | export * from "./layout"; 5 | export * from "./legend"; 6 | export * from "./plot"; 7 | export * from "./series"; 8 | export * from "./title"; 9 | export * from "./x-axis-label"; 10 | export * from "./x-axis-tick"; 11 | export * from "./x-axis"; 12 | export * from "./y-axis-label"; 13 | export * from "./y-axis-tick"; 14 | export * from "./y-axis"; 15 | export * from "./x-axis-line"; 16 | export * from "./y-axis-line"; 17 | export * from "./grid"; 18 | export * from "./gridXLine"; 19 | export * from "./gridYLine"; 20 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./getScale"; 2 | export * from "./line"; 3 | export * from "./data-label"; 4 | export * from "./layout"; 5 | export * from "./legend"; 6 | export * from "./plot"; 7 | export * from "./series"; 8 | export * from "./title"; 9 | export * from "./x-axis-label"; 10 | export * from "./x-axis-tick"; 11 | export * from "./x-axis"; 12 | export * from "./y-axis-label"; 13 | export * from "./y-axis-tick"; 14 | export * from "./y-axis"; 15 | export * from "./x-axis-line"; 16 | export * from "./y-axis-line"; 17 | export * from "./grid"; 18 | export * from "./gridXLine"; 19 | export * from "./gridYLine"; 20 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./getScale"; 2 | export * from "./scatter"; 3 | export * from "./data-label"; 4 | export * from "./layout"; 5 | export * from "./legend"; 6 | export * from "./plot"; 7 | export * from "./series"; 8 | export * from "./title"; 9 | export * from "./x-axis-label"; 10 | export * from "./x-axis-tick"; 11 | export * from "./x-axis"; 12 | export * from "./y-axis-label"; 13 | export * from "./y-axis-tick"; 14 | export * from "./y-axis"; 15 | export * from "./x-axis-line"; 16 | export * from "./y-axis-line"; 17 | export * from "./grid"; 18 | export * from "./gridXLine"; 19 | export * from "./gridYLine"; 20 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/provider.ts: -------------------------------------------------------------------------------- 1 | import { type Widget, Provider, BuildContext } from "@meursyphus/flitter"; 2 | import type { HeatmapConfig } from "./types"; 3 | 4 | const HEATMAP_CHART_CONTEXT_KEY = Symbol("HeatmapChartKey"); 5 | 6 | export function HeatmapConfigProvider({ 7 | child, 8 | value, 9 | }: { 10 | child: Widget; 11 | value: HeatmapConfig; 12 | }): Widget { 13 | return Provider({ 14 | child, 15 | providerKey: HEATMAP_CHART_CONTEXT_KEY, 16 | value, 17 | }); 18 | } 19 | 20 | HeatmapConfigProvider.of = (context: BuildContext): HeatmapConfig => { 21 | return Provider.of(HEATMAP_CHART_CONTEXT_KEY, context); 22 | }; 23 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getScale' 2 | export * from './bar' 3 | export * from './data-label' 4 | export * from './layout' 5 | export * from './legend' 6 | export * from './plot' 7 | export * from './series' 8 | export * from './title' 9 | export * from './x-axis-label' 10 | export * from './x-axis-tick' 11 | export * from './x-axis' 12 | export * from './y-axis-label' 13 | export * from './y-axis-tick' 14 | export * from './y-axis' 15 | export * from './bar-group' 16 | export * from './x-axis-line' 17 | export * from './y-axis-line' 18 | export * from './grid' 19 | export * from './gridXLine' 20 | export * from './gridYLine' 21 | 22 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/provider.ts: -------------------------------------------------------------------------------- 1 | import { Provider, BuildContext, Widget } from "@meursyphus/flitter"; 2 | import { BubbleChartConfig } from "./types"; 3 | 4 | const BUBBLE_CHART_CONTEXT_KEY = Symbol("BubbleChartKey"); 5 | 6 | export function BubbleChartConfigProvider({ 7 | child, 8 | value, 9 | }: { 10 | child: Widget; 11 | value: BubbleChartConfig; 12 | }): Widget { 13 | return Provider({ 14 | child, 15 | providerKey: BUBBLE_CHART_CONTEXT_KEY, 16 | value, 17 | }); 18 | } 19 | 20 | BubbleChartConfigProvider.of = (context: BuildContext): BubbleChartConfig => { 21 | return Provider.of(BUBBLE_CHART_CONTEXT_KEY, context); 22 | }; 23 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/provider.ts: -------------------------------------------------------------------------------- 1 | import { Provider, BuildContext, Widget } from "@meursyphus/flitter"; 2 | import { ScatterChartConfig } from "./types"; 3 | 4 | const SCATTER_CHART_CONTEXT_KEY = Symbol("ScatterChartKey"); 5 | 6 | export function ScatterChartConfigProvider({ 7 | child, 8 | value, 9 | }: { 10 | child: Widget; 11 | value: ScatterChartConfig; 12 | }): Widget { 13 | return Provider({ 14 | child, 15 | providerKey: SCATTER_CHART_CONTEXT_KEY, 16 | value, 17 | }); 18 | } 19 | 20 | ScatterChartConfigProvider.of = (context: BuildContext): ScatterChartConfig => { 21 | return Provider.of(SCATTER_CHART_CONTEXT_KEY, context); 22 | }; 23 | -------------------------------------------------------------------------------- /packages/headless-chart/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": false, 19 | "noUnusedParameters": false, 20 | "noFallthroughCasesInSwitch": true, 21 | "baseUrl": "./", 22 | "paths": { 23 | "@shared/*": ["./src/shared/*"], 24 | "@utils/*": ["./src/utils/*"] 25 | }, 26 | "declaration": true, 27 | "outDir": "./dist" 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/default/layout.ts: -------------------------------------------------------------------------------- 1 | import type { HeatmapCustom } from "../types"; 2 | import { 3 | Column, 4 | Container, 5 | CrossAxisAlignment, 6 | EdgeInsets, 7 | MainAxisAlignment, 8 | MainAxisSize, 9 | Row, 10 | } from "@meursyphus/flitter"; 11 | 12 | export function Layout( 13 | ...[{ title, plot }, { data }]: Parameters 14 | ) { 15 | return Container({ 16 | padding: EdgeInsets.only({ 17 | left: 20, 18 | bottom: 60, 19 | right: 10, 20 | }), 21 | child: Column({ 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | Row({ 25 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 26 | crossAxisAlignment: CrossAxisAlignment.end, 27 | children: [title], 28 | }), 29 | plot, 30 | ], 31 | }), 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /packages/docs/src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection, z } from "astro:content"; 2 | 3 | const docs = defineCollection({ 4 | type: "content", 5 | schema: z.object({ 6 | nav_group: z.string(), 7 | nav_order: z.number().optional(), 8 | nav_title: z.string().optional(), 9 | title: z.string(), 10 | description: z.string().optional(), 11 | tags: z.array(z.string()).optional(), 12 | image: z.string().optional(), 13 | }), 14 | }); 15 | 16 | const charts = defineCollection({ 17 | type: "content", 18 | schema: z.object({ 19 | nav_group: z.string(), 20 | nav_order: z.number().optional(), 21 | title: z.string(), 22 | image: z.string().optional(), 23 | description: z.string(), 24 | files: z.record(z.string()), 25 | solved_files: z.record(z.string()).optional(), 26 | }), 27 | }); 28 | 29 | 30 | export const collections = { 31 | docs: docs, 32 | charts: charts, 33 | }; 34 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/series.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import { Stack, Align, Alignment } from "@meursyphus/flitter"; 3 | 4 | export function Series( 5 | ...[{ points, scale }, context]: Parameters 6 | ) { 7 | const children = points.map((pt) => { 8 | const normX = (pt.x - scale.x.min) / (scale.x.max - scale.x.min); 9 | const normY = (pt.y - scale.y.min) / (scale.y.max - scale.y.min); 10 | 11 | const alignmentX = normX * 2 - 1; 12 | const alignmentY = 1 - normY * 2; 13 | 14 | return Align({ 15 | alignment: new Alignment({ x: alignmentX, y: alignmentY }), 16 | child: context.custom.scatter( 17 | { 18 | label: pt.label, 19 | legend: pt.legend, 20 | index: pt.index, 21 | }, 22 | context, 23 | ), 24 | }); 25 | }); 26 | 27 | return Stack({ children }); 28 | } 29 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/series.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import { Stack, Align, Alignment } from "@meursyphus/flitter"; 3 | 4 | export function Series( 5 | ...[{ points, scale }, context]: Parameters 6 | ) { 7 | const children = points.map((pt) => { 8 | const normX = (pt.x - scale.x.min) / (scale.x.max - scale.x.min); 9 | const normY = (pt.y - scale.y.min) / (scale.y.max - scale.y.min); 10 | 11 | const alignmentX = normX * 2 - 1; 12 | const alignmentY = 1 - normY * 2; 13 | 14 | return Align({ 15 | alignment: new Alignment({ x: alignmentX, y: alignmentY }), 16 | child: context.custom.bubble( 17 | { 18 | value: pt.value, 19 | label: pt.label, 20 | legend: pt.legend, 21 | index: pt.index, 22 | }, 23 | context, 24 | ), 25 | }); 26 | }); 27 | 28 | return Stack({ children }); 29 | } 30 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/grid.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Alignment, 3 | Column, 4 | Container, 5 | Expanded, 6 | Flexible, 7 | MainAxisAlignment, 8 | Opacity, 9 | Row, 10 | Stack, 11 | type Widget, 12 | } from "@meursyphus/flitter"; 13 | import { CartesianCustom } from "./types"; 14 | 15 | export function Grid( 16 | ...[{ x, y, xLine, yLine }]: Parameters 17 | ): Widget { 18 | return Stack({ 19 | children: [ 20 | Column({ 21 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 22 | children: Array.from({ length: y + 1 }, (_,index) => 23 | index === y ? Opacity({ child: xLine, opacity: 0 }) : xLine, 24 | ).flat(), 25 | }), 26 | Row({ 27 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 28 | children: Array.from({ length: x + 1 }, (_, index) => 29 | index === 0 ? Opacity({ child: yLine, opacity: 0 }) : yLine, 30 | ).flat(), 31 | }), 32 | ], 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/getScale.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartData, ScatterChartScale } from "../types"; 2 | import { getValueEdge, refineScale } from "@shared/utils/scale"; 3 | 4 | export function getScale({ datasets }: ScatterChartData): ScatterChartScale { 5 | const xValues: number[] = []; 6 | const yValues: number[] = []; 7 | 8 | datasets.forEach((d) => { 9 | d.data.forEach((p) => { 10 | xValues.push(p.x); 11 | yValues.push(p.y); 12 | }); 13 | }); 14 | 15 | const xEdge = getValueEdge(xValues); 16 | const yEdge = getValueEdge(yValues); 17 | 18 | const roughStepCount = 10; 19 | 20 | const xScale = refineScale({ 21 | min: xEdge.min, 22 | max: xEdge.max, 23 | step: (xEdge.max - xEdge.min) / roughStepCount, 24 | }); 25 | 26 | const yScale = refineScale({ 27 | min: yEdge.min, 28 | max: yEdge.max, 29 | step: (yEdge.max - yEdge.min) / roughStepCount, 30 | }); 31 | 32 | 33 | return { 34 | x: xScale, 35 | y: yScale, 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "headless-chart-workspace", 3 | "description": "", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "dev": "npm run dev --workspace=docs", 8 | "build": "npm run build --workspace=@meursyphus/headless-chart", 9 | "start": "npm run dev", 10 | "docs:start": "npm run dev", 11 | "docs:build": "npm run build --workspace=docs", 12 | "predocs:build": "npm run build", 13 | "lunaria:build": "lunaria build && cp -r dist/lunaria/index.html packages/docs/public/lunaria.html", 14 | "lunaria:preview": "lunaria preview" 15 | }, 16 | "author": "meursyphus", 17 | "license": "ISC", 18 | "workspaces": [ 19 | "packages/*" 20 | ], 21 | "devDependencies": { 22 | "@meursyphus/flitter": "^2.0.3", 23 | "@meursyphus/flitter-react": "^0.0.8", 24 | "@meursyphus/flitter-svelte": "^2.0.0-alpha.1" 25 | }, 26 | "dependencies": { 27 | "@lunariajs/core": "^0.1.1", 28 | "astro": "^5.0.3", 29 | "vite-plugin-dts": "^4.2.4" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/layout.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartCustom } from "../types"; 2 | import { 3 | Column, 4 | Container, 5 | CrossAxisAlignment, 6 | EdgeInsets, 7 | MainAxisAlignment, 8 | MainAxisSize, 9 | Row, 10 | } from "@meursyphus/flitter"; 11 | 12 | export function Layout( 13 | ...[{ title, legends, plot }]: Parameters 14 | ) { 15 | return Container({ 16 | padding: EdgeInsets.only({ 17 | left: 20, 18 | bottom: 60, 19 | right: 10, 20 | }), 21 | child: Column({ 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | Row({ 25 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 26 | crossAxisAlignment: CrossAxisAlignment.end, 27 | children: [ 28 | title, 29 | Row({ 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | mainAxisSize: MainAxisSize.min, 32 | children: legends, 33 | }), 34 | ], 35 | }), 36 | plot, 37 | ], 38 | }), 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/layout.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom } from "../types"; 2 | import { 3 | Column, 4 | Container, 5 | CrossAxisAlignment, 6 | EdgeInsets, 7 | MainAxisAlignment, 8 | MainAxisSize, 9 | Row, 10 | } from "@meursyphus/flitter"; 11 | 12 | export function Layout( 13 | ...[{ title, legends, plot }, { data }]: Parameters 14 | ) { 15 | return Container({ 16 | padding: EdgeInsets.only({ 17 | left: 20, 18 | bottom: 60, 19 | right: 10, 20 | }), 21 | child: Column({ 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | Row({ 25 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 26 | crossAxisAlignment: CrossAxisAlignment.end, 27 | children: [ 28 | title, 29 | Row({ 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | mainAxisSize: MainAxisSize.min, 32 | children: legends, 33 | }), 34 | ], 35 | }), 36 | plot, 37 | ], 38 | }), 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/layout.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom } from "../types"; 2 | import { 3 | Column, 4 | Container, 5 | CrossAxisAlignment, 6 | EdgeInsets, 7 | MainAxisAlignment, 8 | MainAxisSize, 9 | Row, 10 | } from "@meursyphus/flitter"; 11 | 12 | export function Layout( 13 | ...[{ title, legends, plot }, { data }]: Parameters 14 | ) { 15 | return Container({ 16 | padding: EdgeInsets.only({ 17 | left: 20, 18 | bottom: 60, 19 | right: 10, 20 | }), 21 | child: Column({ 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | Row({ 25 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 26 | crossAxisAlignment: CrossAxisAlignment.end, 27 | children: [ 28 | title, 29 | Row({ 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | mainAxisSize: MainAxisSize.min, 32 | children: legends, 33 | }), 34 | ], 35 | }), 36 | plot, 37 | ], 38 | }), 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/default/layout.ts: -------------------------------------------------------------------------------- 1 | import type { ScatterChartCustom } from "../types"; 2 | import { 3 | Column, 4 | Container, 5 | CrossAxisAlignment, 6 | EdgeInsets, 7 | MainAxisAlignment, 8 | MainAxisSize, 9 | Row, 10 | } from "@meursyphus/flitter"; 11 | 12 | export function Layout( 13 | ...[{ title, legends, plot }]: Parameters 14 | ) { 15 | return Container({ 16 | padding: EdgeInsets.only({ 17 | left: 20, 18 | bottom: 60, 19 | right: 10, 20 | }), 21 | child: Column({ 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | Row({ 25 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 26 | crossAxisAlignment: CrossAxisAlignment.end, 27 | children: [ 28 | title, 29 | Row({ 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | mainAxisSize: MainAxisSize.min, 32 | children: legends, 33 | }), 34 | ], 35 | }), 36 | plot, 37 | ], 38 | }), 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 meusyphus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/layout.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianCustom } from "./types"; 2 | import { 3 | Column, 4 | Container, 5 | CrossAxisAlignment, 6 | EdgeInsets, 7 | MainAxisAlignment, 8 | MainAxisSize, 9 | Row, 10 | type Widget, 11 | } from "@meursyphus/flitter"; 12 | 13 | export function Layout( 14 | ...[{ title, legends, plot }]: Parameters 15 | ): Widget { 16 | return Container({ 17 | padding: EdgeInsets.only({ 18 | left: 20, 19 | bottom: 60, 20 | right: 10, 21 | }), 22 | child: Column({ 23 | crossAxisAlignment: CrossAxisAlignment.start, 24 | children: [ 25 | Row({ 26 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 27 | crossAxisAlignment: CrossAxisAlignment.end, 28 | children: [ 29 | title, 30 | Row({ 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | mainAxisSize: MainAxisSize.min, 33 | children: legends, 34 | }), 35 | ], 36 | }), 37 | plot, 38 | ], 39 | }), 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/layout.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from '../types'; 2 | import { 3 | Column, 4 | Container, 5 | CrossAxisAlignment, 6 | EdgeInsets, 7 | MainAxisAlignment, 8 | MainAxisSize, 9 | Row 10 | } from '@meursyphus/flitter'; 11 | 12 | export function Layout( 13 | ...[{ title, legends, plot }, { data, direction }]: Parameters 14 | ) { 15 | const maxLabelLength = Math.max(...data.labels.map((label) => label.length)); 16 | return Container({ 17 | padding: EdgeInsets.only({ 18 | left: 20 + (direction === 'horizontal' ? maxLabelLength * 3 : 0), 19 | bottom: 60, 20 | right: 10 21 | }), 22 | child: Column({ 23 | crossAxisAlignment: CrossAxisAlignment.start, 24 | children: [ 25 | Row({ 26 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 27 | crossAxisAlignment: CrossAxisAlignment.end, 28 | children: [ 29 | title, 30 | Row({ 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | mainAxisSize: MainAxisSize.min, 33 | children: legends 34 | }) 35 | ] 36 | }), 37 | plot 38 | ] 39 | }) 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/plot.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianCustom } from "./types"; 2 | import { 3 | Container, 4 | FractionalTranslation, 5 | Stack, 6 | Positioned, 7 | type Widget, 8 | } from "@meursyphus/flitter"; 9 | 10 | export function Plot({ 11 | series, 12 | xAxis, 13 | yAxis, 14 | grid, 15 | }: Parameters[0]): Widget { 16 | return Stack({ 17 | children: [ 18 | Positioned({ 19 | top: 0, 20 | left: 0, 21 | bottom: 0, 22 | child: FractionalTranslation({ 23 | translation: { x: -1, y: 0 }, 24 | child: yAxis, 25 | }), 26 | }), 27 | Positioned({ 28 | bottom: 0, 29 | left: 0, 30 | right: 0, 31 | child: FractionalTranslation({ 32 | translation: { x: 0, y: 1 }, 33 | child: xAxis, 34 | }), 35 | }), 36 | Positioned({ 37 | bottom: 0, 38 | left: 0, 39 | child: FractionalTranslation({ 40 | translation: { x: -1, y: 1 }, 41 | child: Container({ 42 | color: "black", 43 | width: 1, 44 | height: 1, 45 | }), 46 | }), 47 | }), 48 | grid, 49 | series, 50 | ], 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/x-axis.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianCustom } from "./types"; 2 | import { 3 | Column, 4 | FractionalTranslation, 5 | MainAxisAlignment, 6 | MainAxisSize, 7 | Offset, 8 | Row, 9 | type Widget, 10 | } from "@meursyphus/flitter"; 11 | import { IgnoreSize } from "@utils/index"; 12 | 13 | export function XAxis( 14 | ...[{ labels, tick, line }, { type }]: Parameters 15 | ): Widget { 16 | return Column({ 17 | mainAxisSize: MainAxisSize.min, 18 | children: [ 19 | line, 20 | Row({ 21 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 22 | children: Array.from( 23 | { length: labels.length + (type === "label" ? 1 : 0) }, 24 | (_, index) => 25 | FractionalTranslation({ 26 | translation: new Offset({ x: index === 0 ? -1 : 0, y: 0 }), 27 | child: tick, 28 | }), 29 | ), 30 | }), 31 | Row({ 32 | mainAxisAlignment: 33 | type === "label" 34 | ? MainAxisAlignment.spaceAround 35 | : MainAxisAlignment.spaceBetween, 36 | children: labels.map((child) => 37 | IgnoreSize({ child, ignoreWidth: true }), 38 | ), 39 | }), 40 | ], 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro check && astro build", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "@astrojs/check": "^0.9.4", 14 | "@astrojs/mdx": "^4.0.1", 15 | "@astrojs/react": "^4.0.0", 16 | "@astrojs/sitemap": "^3.2.1", 17 | "@astrojs/svelte": "^7.0.1", 18 | "@astrojs/tailwind": "^5.1.3", 19 | "@codesandbox/sandpack-react": "^2.13.10", 20 | "@meursyphus/flitter-chart": "^0.0.9", 21 | "@monaco-editor/react": "^4.6.0", 22 | "@tailwindcss/typography": "^0.5.10", 23 | "@types/react": "^18.2.64", 24 | "@types/react-dom": "^18.2.21", 25 | "astro": "^5.0.3", 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0", 28 | "react-markdown": "^9.0.1", 29 | "tailwindcss": "^3.4.1", 30 | "typescript": "^5.4.2" 31 | }, 32 | "devDependencies": { 33 | "@astrojs/cloudflare": "^12.0.1", 34 | "@tailwindcss/typography": "^0.5.10", 35 | "prettier": "^3.3.1", 36 | "prettier-plugin-astro": "^0.13.0", 37 | "prettier-plugin-tailwindcss": "^0.6.2" 38 | }, 39 | "engines": { 40 | "node": ">=18.14.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/default/getScale.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleChartData, BubbleChartScale } from "../types"; 2 | import { getValueEdge, refineScale } from "@shared/utils/scale"; 3 | 4 | export function getScale({ datasets }: BubbleChartData): BubbleChartScale { 5 | const xValues: number[] = []; 6 | const yValues: number[] = []; 7 | const values: number[] = []; 8 | 9 | datasets.forEach((d) => { 10 | d.data.forEach((p) => { 11 | xValues.push(p.x); 12 | yValues.push(p.y); 13 | values.push(p.value); 14 | }); 15 | }); 16 | 17 | const xEdge = getValueEdge(xValues); 18 | const yEdge = getValueEdge(yValues); 19 | const valueEdge = getValueEdge(values); 20 | 21 | const roughStepCount = 10; 22 | 23 | const xScale = refineScale({ 24 | min: xEdge.min, 25 | max: xEdge.max, 26 | step: (xEdge.max - xEdge.min) / roughStepCount, 27 | }); 28 | 29 | const yScale = refineScale({ 30 | min: yEdge.min, 31 | max: yEdge.max, 32 | step: (yEdge.max - yEdge.min) / roughStepCount, 33 | }); 34 | 35 | const valueScale = refineScale({ 36 | min: valueEdge.min, 37 | max: valueEdge.max, 38 | step: (valueEdge.max - valueEdge.min) / roughStepCount, 39 | }); 40 | 41 | return { 42 | x: xScale, 43 | y: yScale, 44 | value: valueScale, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/types.ts: -------------------------------------------------------------------------------- 1 | import type { Widget } from "@meursyphus/flitter"; 2 | 3 | type ConfigArgs = (args: T, context: HeatmapConfig) => Widget; 4 | 5 | export type HeatmapCustom = { 6 | layout: ConfigArgs<{ title: Widget; plot: Widget }>; 7 | plot: ConfigArgs<{ xAxis: Widget; yAxis: Widget; heatmap: Widget }>; 8 | 9 | xAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 10 | yAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 11 | xAxisLabel: ConfigArgs<{ name: string; index: number }>; 12 | yAxisLabel: ConfigArgs<{ name: string; index: number }>; 13 | xAxisLine: ConfigArgs; 14 | yAxisLine: ConfigArgs; 15 | xAxisTick: ConfigArgs; 16 | yAxisTick: ConfigArgs; 17 | 18 | heatmap: ConfigArgs<{ segments: Widget[][] }>; 19 | segment: ConfigArgs<{ value: number; xIndex: number; yIndex: number }>; 20 | 21 | title: ConfigArgs<{ name: string }>; 22 | }; 23 | 24 | export type HeatmapData = { 25 | xLabels: string[]; 26 | yLabels: string[]; 27 | // segments are 2-dimensional array [yIndex][xIndex] 28 | values: number[][]; 29 | }; 30 | 31 | export type HeatmapScale = { 32 | min: number; 33 | max: number; 34 | }; 35 | 36 | export type HeatmapConfig = { 37 | custom: HeatmapCustom; 38 | data: HeatmapData; 39 | scale: HeatmapScale; 40 | title: string; 41 | }; 42 | 43 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/default/bar-group.ts: -------------------------------------------------------------------------------- 1 | import type { BarChartCustom } from '../types'; 2 | import { 3 | Alignment, 4 | Axis, 5 | Container, 6 | CrossAxisAlignment, 7 | EdgeInsets, 8 | Flex, 9 | FractionallySizedBox, 10 | MainAxisAlignment, 11 | Padding 12 | } from '@meursyphus/flitter'; 13 | 14 | export function BarGroup( 15 | ...[{ bars, values }, { direction, scale }]: Parameters 16 | ) { 17 | const total = scale.max - scale.min; 18 | const ratios = values.map((value) => value / total); 19 | 20 | return Container({ 21 | width: Infinity, 22 | height: Infinity, 23 | child: Flex({ 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | crossAxisAlignment: 26 | direction === 'vertical' ? CrossAxisAlignment.end : CrossAxisAlignment.start, 27 | direction: direction === 'horizontal' ? Axis.vertical : Axis.horizontal, 28 | children: bars.map((bar, index) => { 29 | return FractionallySizedBox({ 30 | alignment: direction === 'horizontal' ? Alignment.bottomLeft : Alignment.centerLeft, 31 | widthFactor: direction === 'horizontal' ? ratios[index] : undefined, 32 | heightFactor: direction === 'vertical' ? ratios[index] : undefined, 33 | child: Padding({ 34 | padding: EdgeInsets.symmetric( 35 | direction === 'vertical' ? { horizontal: 2 } : { vertical: 2 } 36 | ), 37 | child: bar 38 | }) 39 | }); 40 | }) 41 | }) 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/types.ts: -------------------------------------------------------------------------------- 1 | import type { Widget } from "@meursyphus/flitter"; 2 | 3 | type ConfigArgs = ( 4 | args: T, 5 | option: OPTION, 6 | ) => Widget; 7 | 8 | export type CartesianCustom = { 9 | xAxis: ConfigArgs< 10 | { line: Widget; labels: Widget[]; tick: Widget }, 11 | { type: "value" | "label" } 12 | >; 13 | yAxis: ConfigArgs< 14 | { line: Widget; labels: Widget[]; tick: Widget }, 15 | { type: "value" | "label" } 16 | >; 17 | xAxisLabel: ConfigArgs<{ name: string; index: number }>; 18 | yAxisLabel: ConfigArgs<{ name: string; index: number }>; 19 | xAxisTick: ConfigArgs; 20 | yAxisTick: ConfigArgs; 21 | layout: ConfigArgs<{ title: Widget; legends: Widget[]; plot: Widget }>; 22 | plot: ConfigArgs<{ xAxis: Widget; yAxis: Widget; series: Widget, grid: Widget }>; 23 | legend: ConfigArgs<{ name: string; index: number }>; 24 | title: ConfigArgs<{ name: string }>; 25 | dataLabel: ConfigArgs<{ value: number; label: string; legend: string }>; 26 | xAxisLine: ConfigArgs; 27 | yAxisLine: ConfigArgs; 28 | grid: ConfigArgs<{ x: number; y: number; xLine: Widget; yLine: Widget }>; 29 | gridXLine: ConfigArgs; 30 | gridYLine: ConfigArgs; 31 | }; 32 | 33 | export type CartesianData = { 34 | labels: string[]; 35 | datasets: { legend: string; values: number[] }[]; 36 | }; 37 | 38 | export type CartesianScale = { 39 | min: number; 40 | max: number; 41 | step: number; 42 | }; 43 | -------------------------------------------------------------------------------- /packages/headless-chart/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/headless-chart/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Alignment, 3 | ConstraintsTransformBox, 4 | Widget, 5 | SizedBox, 6 | } from "@meursyphus/flitter"; 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | export function classToFn any>( 9 | Constructor: V, 10 | ) { 11 | return (...arr: ConstructorParameters) => 12 | new Constructor(...arr) as InstanceType; 13 | } 14 | 15 | export type PickPartial = Omit & 16 | Partial>; 17 | 18 | export function IgnoreSize({ 19 | ignoreHeight = false, 20 | ignoreWidth = false, 21 | child, 22 | alignment, 23 | }: { 24 | ignoreWidth?: boolean; 25 | ignoreHeight?: boolean; 26 | child?: Widget; 27 | alignment?: Alignment; 28 | }) { 29 | let constraintsTransform; 30 | 31 | if (ignoreWidth && ignoreHeight) { 32 | constraintsTransform = ConstraintsTransformBox.maxUnconstrained; 33 | } else if (ignoreWidth) { 34 | constraintsTransform = ConstraintsTransformBox.maxWidthUnconstrained; 35 | } else if (ignoreHeight) { 36 | constraintsTransform = ConstraintsTransformBox.maxHeightUnconstrained; 37 | } else { 38 | constraintsTransform = ConstraintsTransformBox.unmodified; 39 | } 40 | 41 | return SizedBox({ 42 | width: ignoreWidth ? 0 : undefined, 43 | height: ignoreHeight ? 0 : undefined, 44 | child: ConstraintsTransformBox({ 45 | alignment, 46 | constraintsTransform, 47 | child, 48 | }), 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /packages/headless-chart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meursyphus/headless-chart", 3 | "private": false, 4 | "version": "0.0.8", 5 | "type": "module", 6 | "description": "Highly customizable chart library built on top of Flitter framework", 7 | "keywords": [ 8 | "chart", 9 | "flitter", 10 | "visualization", 11 | "react", 12 | "headless" 13 | ], 14 | "author": "meursyphus", 15 | "license": "MIT", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/meursyphus/headless-chart" 19 | }, 20 | "scripts": { 21 | "dev": "vite", 22 | "build": "tsc && vite build", 23 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 24 | "preview": "vite preview", 25 | "prepare": "npm run build" 26 | }, 27 | "dependencies": { 28 | "@meursyphus/flitter": "^2.0.0" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^20.10.4", 32 | "@typescript-eslint/eslint-plugin": "^6.14.0", 33 | "@typescript-eslint/parser": "^6.14.0", 34 | "@vitejs/plugin-react": "^4.2.1", 35 | "eslint": "^8.55.0", 36 | "typescript": "^5.2.2", 37 | "vite": "^5.0.8", 38 | "vite-plugin-dts": "^3.6.4", 39 | "vite-tsconfig-paths": "^4.3.2" 40 | }, 41 | "main": "./dist/headless-chart.es.js", 42 | "module": "./dist/headless-chart.es.js", 43 | "types": "./dist/src/index.d.ts", 44 | "files": [ 45 | "dist" 46 | ], 47 | "publishConfig": { 48 | "access": "public" 49 | }, 50 | "peerDependencies": { 51 | "@meursyphus/flitter": "^2.0.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lunaria.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@lunariajs/core/config.schema.json", 3 | "repository": { 4 | "name": "meursyphus/headless-chart", 5 | "rootDir": "." 6 | }, 7 | "defaultLocale": { 8 | "label": "English", 9 | "lang": "en" 10 | }, 11 | "locales": [ 12 | { 13 | "label": "English", 14 | "lang": "en" 15 | }, 16 | { 17 | "label": "한국어", 18 | "lang": "ko" 19 | }, 20 | { 21 | "label": "简体中文", 22 | "lang": "zh-cn" 23 | }, 24 | { 25 | "label": "正體中文", 26 | "lang": "zh-tw" 27 | }, 28 | { 29 | "label": "日本語", 30 | "lang": "ja" 31 | }, 32 | { 33 | "label": "العربية", 34 | "lang": "ar" 35 | }, 36 | { 37 | "label": "Deutsch", 38 | "lang": "de" 39 | }, 40 | { 41 | "label": "Español", 42 | "lang": "es" 43 | }, 44 | { 45 | "label": "Français", 46 | "lang": "fr" 47 | }, 48 | { 49 | "label": "हिन्दी", 50 | "lang": "hi" 51 | }, 52 | { 53 | "label": "Italiano", 54 | "lang": "it" 55 | }, 56 | { 57 | "label": "Polski", 58 | "lang": "pl" 59 | }, 60 | { 61 | "label": "Português do Brasil", 62 | "lang": "pt-br" 63 | }, 64 | { 65 | "label": "Русский", 66 | "lang": "ru" 67 | } 68 | ], 69 | "files": [ 70 | { 71 | "location": "packages/docs/src/content/docs/**/*.mdx", 72 | "pattern": "packages/docs/src/content/docs/@lang/@path", 73 | "type": "universal" 74 | } 75 | ] 76 | } -------------------------------------------------------------------------------- /packages/docs/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | import react from "@astrojs/react"; 3 | import svelte from "@astrojs/svelte"; 4 | import tailwind from "@astrojs/tailwind"; 5 | import mdx from "@astrojs/mdx"; 6 | import sitemap from '@astrojs/sitemap'; 7 | import cloudflare from "@astrojs/cloudflare"; 8 | 9 | // https://astro.build/config 10 | export default defineConfig({ 11 | site: "https://headless-chart.pages.dev", 12 | output: "server", 13 | adapter: cloudflare({ 14 | 15 | }), 16 | integrations: [ 17 | react(), 18 | svelte(), 19 | tailwind(), 20 | mdx(), 21 | sitemap(), 22 | ], 23 | markdown: { 24 | shikiConfig: { 25 | // Choose from Shiki's built-in themes (or add your own) 26 | // https://github.com/shikijs/shiki/blob/main/docs/themes.md 27 | theme: "dracula", 28 | // Alternatively, provide multiple themes 29 | // https://shiki.style/guide/dual-themes#light-dark-dual-themes 30 | experimentalThemes: { 31 | light: "github-dark", 32 | dark: "github-light", 33 | }, 34 | // Add custom languages 35 | // Note: Shiki has countless langs built-in, including .astro! 36 | // https://github.com/shikijs/shiki/blob/main/docs/languages.md 37 | langs: [], 38 | // Enable word wrap to prevent horizontal scrolling 39 | wrap: true, 40 | // Add custom transformers: https://shiki.style/guide/transformers 41 | // Find common transformers: https://shiki.style/packages/transformers 42 | transformers: [], 43 | }, 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/y-axis.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianCustom } from "./types"; 2 | import { 3 | Column, 4 | CrossAxisAlignment, 5 | FractionalTranslation, 6 | MainAxisAlignment, 7 | MainAxisSize, 8 | Offset, 9 | Row, 10 | VerticalDirection, 11 | type Widget, 12 | } from "@meursyphus/flitter"; 13 | import { IgnoreSize } from "@utils/index"; 14 | 15 | export function YAxis( 16 | ...[{ labels, tick, line }, { type }]: Parameters 17 | ): Widget { 18 | const tickCount = labels.length + (type === "label" ? 1 : 0); 19 | return Row({ 20 | mainAxisSize: MainAxisSize.min, 21 | children: [ 22 | Column({ 23 | crossAxisAlignment: CrossAxisAlignment.end, 24 | verticalDirection: 25 | type === "value" ? VerticalDirection.up : VerticalDirection.down, 26 | mainAxisAlignment: 27 | type === "label" 28 | ? MainAxisAlignment.spaceAround 29 | : MainAxisAlignment.spaceBetween, 30 | children: labels.map((child) => 31 | IgnoreSize({ child, ignoreHeight: true }), 32 | ), 33 | }), 34 | Column({ 35 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 36 | children: Array.from({ length: tickCount }, (_, index) => 37 | FractionalTranslation({ 38 | translation: new Offset({ 39 | y: index === tickCount - 1 ? 1 : 0, 40 | x: 0, 41 | }), 42 | child: tick, 43 | }), 44 | ), 45 | }), 46 | line, 47 | ], 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/types.ts: -------------------------------------------------------------------------------- 1 | import type { Widget } from "@meursyphus/flitter"; 2 | 3 | type ConfigArgs = (args: T, context: LineChartConfig) => Widget; 4 | 5 | export type LineChartCustom = { 6 | xAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 7 | yAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 8 | xAxisLabel: ConfigArgs<{ name: string; index: number }>; 9 | yAxisLabel: ConfigArgs<{ name: string; index: number }>; 10 | xAxisTick: ConfigArgs; 11 | yAxisTick: ConfigArgs; 12 | series: ConfigArgs<{ lines: Widget[] }>; 13 | line: ConfigArgs<{ values: number[]; legend: string; index: number }>; 14 | layout: ConfigArgs<{ title: Widget; legends: Widget[]; plot: Widget }>; 15 | plot: ConfigArgs<{ xAxis: Widget; yAxis: Widget; series: Widget; grid: Widget }>; 16 | legend: ConfigArgs<{ name: string; index: number }>; 17 | title: ConfigArgs<{ name: string }>; 18 | dataLabel: ConfigArgs<{ value: number; label: string; legend: string }>; 19 | xAxisLine: ConfigArgs; 20 | yAxisLine: ConfigArgs; 21 | grid: ConfigArgs<{ xLine: Widget; yLine: Widget }>; 22 | gridXLine: ConfigArgs; 23 | gridYLine: ConfigArgs; 24 | }; 25 | 26 | export type LineChartData = { 27 | labels: string[]; 28 | datasets: { legend: string; values: number[] }[]; 29 | }; 30 | 31 | export type LineChartScale = { 32 | min: number; 33 | max: number; 34 | step: number; 35 | }; 36 | 37 | export type LineChartConfig = { 38 | custom: LineChartCustom; 39 | data: LineChartData; 40 | scale: LineChartScale; 41 | title: string; 42 | }; 43 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/types.ts: -------------------------------------------------------------------------------- 1 | import type { Widget } from "@meursyphus/flitter"; 2 | 3 | type ConfigArgs = (args: T, context: AreaChartConfig) => Widget; 4 | 5 | export type AreaChartCustom = { 6 | xAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 7 | yAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 8 | xAxisLabel: ConfigArgs<{ name: string; index: number }>; 9 | yAxisLabel: ConfigArgs<{ name: string; index: number }>; 10 | xAxisTick: ConfigArgs; 11 | yAxisTick: ConfigArgs; 12 | series: ConfigArgs<{ areas: Widget[] }>; 13 | area: ConfigArgs<{ values: number[]; legend: string; index: number }>; 14 | layout: ConfigArgs<{ title: Widget; legends: Widget[]; plot: Widget }>; 15 | plot: ConfigArgs<{ 16 | xAxis: Widget; 17 | yAxis: Widget; 18 | series: Widget; 19 | grid: Widget; 20 | }>; 21 | legend: ConfigArgs<{ name: string; index: number }>; 22 | title: ConfigArgs<{ name: string }>; 23 | dataLabel: ConfigArgs<{ value: number; label: string; legend: string }>; 24 | xAxisLine: ConfigArgs; 25 | yAxisLine: ConfigArgs; 26 | grid: ConfigArgs<{ xLine: Widget; yLine: Widget }>; 27 | gridXLine: ConfigArgs; 28 | gridYLine: ConfigArgs; 29 | }; 30 | 31 | export type AreaChartData = { 32 | labels: string[]; 33 | datasets: { legend: string; values: number[] }[]; 34 | }; 35 | 36 | export type AreaChartScale = { 37 | min: number; 38 | max: number; 39 | step: number; 40 | }; 41 | 42 | export type AreaChartConfig = { 43 | custom: AreaChartCustom; 44 | data: AreaChartData; 45 | scale: AreaChartScale; 46 | title: string; 47 | }; 48 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/types.ts: -------------------------------------------------------------------------------- 1 | import type { Widget } from '@meursyphus/flitter'; 2 | 3 | type ConfigArgs = (args: T, context: BarChartConfig) => Widget; 4 | 5 | export type BarChartCustom = { 6 | barGroup: ConfigArgs<{ bars: Widget[]; index: number; label: string; values: number[] }>; 7 | bar: ConfigArgs<{ value: number; label: string; legend: string; index: number }>; 8 | xAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 9 | yAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 10 | xAxisLabel: ConfigArgs<{ name: string; index: number }>; 11 | yAxisLabel: ConfigArgs<{ name: string; index: number }>; 12 | xAxisTick: ConfigArgs; 13 | yAxisTick: ConfigArgs; 14 | series: ConfigArgs<{ barGroups: Widget[] }>; 15 | layout: ConfigArgs<{ title: Widget; legends: Widget[]; plot: Widget }>; 16 | plot: ConfigArgs<{ xAxis: Widget; yAxis: Widget; series: Widget; grid: Widget }>; 17 | legend: ConfigArgs<{ name: string; index: number }>; 18 | title: ConfigArgs<{ name: string }>; 19 | dataLabel: ConfigArgs<{ value: number; label: string; legend: string }>; 20 | xAxisLine: ConfigArgs; 21 | yAxisLine: ConfigArgs; 22 | grid: ConfigArgs<{ xLine: Widget; yLine: Widget }>; 23 | gridXLine: ConfigArgs; 24 | gridYLine: ConfigArgs; 25 | }; 26 | 27 | export type BarChartData = { 28 | labels: string[]; 29 | datasets: { legend: string; values: number[] }[]; 30 | }; 31 | 32 | export type BarChartScale = { 33 | min: number; 34 | max: number; 35 | step: number; 36 | }; 37 | 38 | type BarChartDirection = 'vertical' | 'horizontal'; 39 | 40 | export type BarChartConfig = { 41 | custom: BarChartCustom; 42 | data: BarChartData; 43 | scale: BarChartScale; 44 | title: string; 45 | direction: BarChartDirection; 46 | }; 47 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/default/area.ts: -------------------------------------------------------------------------------- 1 | import type { AreaChartCustom, AreaChartScale } from "../types"; 2 | import { CustomPaint, Path } from "@meursyphus/flitter"; 3 | 4 | export function Line( 5 | ...[{ values }, { scale }]: Parameters 6 | ) { 7 | return CustomPaint({ 8 | painter: { 9 | svg: { 10 | createDefaultSvgEl: (context) => { 11 | return { 12 | line: context.createSvgEl("path"), 13 | }; 14 | }, 15 | paint: ({ line }, { width, height }) => { 16 | const path = createLinePath({ values, scale, width, height }); 17 | line.setAttribute("fill", "none"); 18 | line.setAttribute("stroke", "black"); 19 | line.setAttribute("stroke-width", "1"); 20 | line.setAttribute("d", path.getD()); 21 | }, 22 | }, 23 | canvas: { 24 | paint: (context, { width, height }) => { 25 | const path = createLinePath({ values, scale, width, height }); 26 | context.canvas.strokeStyle = "black"; 27 | context.canvas.lineWidth = 1; 28 | context.canvas.stroke(path.toCanvasPath()); 29 | }, 30 | }, 31 | }, 32 | }); 33 | } 34 | 35 | function createLinePath({ 36 | values, 37 | scale, 38 | width, 39 | height, 40 | }: { 41 | values: number[]; 42 | scale: AreaChartScale; 43 | width: number; 44 | height: number; 45 | }) { 46 | const path = new Path(); 47 | const points = values.map((value, index) => { 48 | const y = height - (height * value) / (scale.max - scale.min); 49 | const x = (index * width) / (values.length - 1); 50 | return { x, y }; 51 | }); 52 | path.moveTo(points[0]); 53 | points.slice(1).forEach((point) => { 54 | path.lineTo(point); 55 | }); 56 | return path; 57 | } 58 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/default/line.ts: -------------------------------------------------------------------------------- 1 | import type { LineChartCustom, LineChartScale } from "../types"; 2 | import { CustomPaint, Path } from "@meursyphus/flitter"; 3 | 4 | export function Line( 5 | ...[{ values }, { scale }]: Parameters 6 | ) { 7 | return CustomPaint({ 8 | painter: { 9 | svg: { 10 | createDefaultSvgEl: (context) => { 11 | return { 12 | line: context.createSvgEl("path"), 13 | }; 14 | }, 15 | paint: ({ line }, { width, height }) => { 16 | const path = createLinePath({ values, scale, width, height }); 17 | line.setAttribute("fill", "none"); 18 | line.setAttribute("stroke", "black"); 19 | line.setAttribute("stroke-width", "1"); 20 | line.setAttribute("d", path.getD()); 21 | }, 22 | }, 23 | canvas: { 24 | paint: (context, { width, height }) => { 25 | const path = createLinePath({ values, scale, width, height }); 26 | context.canvas.strokeStyle = "black"; 27 | context.canvas.lineWidth = 1; 28 | context.canvas.stroke(path.toCanvasPath()); 29 | }, 30 | }, 31 | }, 32 | }); 33 | } 34 | 35 | function createLinePath({ 36 | values, 37 | scale, 38 | width, 39 | height, 40 | }: { 41 | values: number[]; 42 | scale: LineChartScale; 43 | width: number; 44 | height: number; 45 | }) { 46 | const path = new Path(); 47 | const points = values.map((value, index) => { 48 | const y = height - (height * value) / (scale.max - scale.min); 49 | const x = (index * width) / (values.length - 1); 50 | return { x, y }; 51 | }); 52 | path.moveTo(points[0]); 53 | points.slice(1).forEach((point) => { 54 | path.lineTo(point); 55 | }); 56 | return path; 57 | } 58 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/index.ts: -------------------------------------------------------------------------------- 1 | import { type Widget } from "@meursyphus/flitter"; 2 | import type { 3 | ScatterChartCustom, 4 | ScatterChartData, 5 | ScatterChartScale, 6 | } from "./types"; 7 | import { ScatterChartConfigProvider } from "./provider"; 8 | import * as Default from "./default"; 9 | import Chart from "./chart"; 10 | 11 | export default function BubbleChart({ 12 | custom = {}, 13 | data, 14 | title = "", 15 | getScale = Default.getScale, 16 | }: { 17 | custom?: Partial; 18 | title?: string; 19 | data: ScatterChartData; 20 | getScale?: (data: ScatterChartData) => ScatterChartScale; 21 | }): Widget { 22 | const mergedConfig: ScatterChartCustom = { 23 | scatter: custom.scatter ?? Default.Scatter, 24 | xAxis: custom.xAxis ?? Default.XAxis, 25 | xAxisLabel: custom.xAxisLabel ?? Default.XAxisLabel, 26 | xAxisTick: custom.xAxisTick ?? Default.XAxisTick, 27 | xAxisLine: custom.xAxisLine ?? Default.XAxisLine, 28 | yAxis: custom.yAxis ?? Default.YAxis, 29 | yAxisLabel: custom.yAxisLabel ?? Default.YAxisLabel, 30 | yAxisTick: custom.yAxisTick ?? Default.YAxisTick, 31 | yAxisLine: custom.yAxisLine ?? Default.YAxisLine, 32 | series: custom.series ?? Default.Series, 33 | layout: custom.layout ?? Default.Layout, 34 | plot: custom.plot ?? Default.Plot, 35 | legend: custom.legend ?? Default.Legend, 36 | title: custom.title ?? Default.Title, 37 | dataLabel: custom.dataLabel ?? Default.DataLabel, 38 | grid: custom.grid ?? Default.Grid, 39 | gridXLine: custom.gridXLine ?? Default.GridXLine, 40 | gridYLine: custom.gridYLine ?? Default.GridYLine, 41 | }; 42 | 43 | const scale = getScale(data); 44 | 45 | return ScatterChartConfigProvider({ 46 | value: { 47 | custom: mergedConfig, 48 | data, 49 | scale, 50 | title, 51 | }, 52 | child: new Chart(), 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/index.ts: -------------------------------------------------------------------------------- 1 | import { type Widget, StatelessWidget } from "@meursyphus/flitter"; 2 | import type { 3 | BubbleChartCustom, 4 | BubbleChartData, 5 | BubbleChartScale, 6 | } from "./types"; 7 | import { BubbleChartConfigProvider } from "./provider"; 8 | import * as Default from "./default"; 9 | import Chart from "./chart"; 10 | 11 | 12 | export default function BubbleChart({ 13 | custom = {}, 14 | data, 15 | title = "", 16 | getScale = Default.getScale, 17 | }: { 18 | custom?: Partial; 19 | title?: string; 20 | data: BubbleChartData; 21 | getScale?: (data: BubbleChartData) => BubbleChartScale; 22 | }): Widget { 23 | const mergedConfig: BubbleChartCustom = { 24 | bubble: custom.bubble ?? Default.Bubble, 25 | xAxis: custom.xAxis ?? Default.XAxis, 26 | xAxisLabel: custom.xAxisLabel ?? Default.XAxisLabel, 27 | xAxisTick: custom.xAxisTick ?? Default.XAxisTick, 28 | xAxisLine: custom.xAxisLine ?? Default.XAxisLine, 29 | yAxis: custom.yAxis ?? Default.YAxis, 30 | yAxisLabel: custom.yAxisLabel ?? Default.YAxisLabel, 31 | yAxisTick: custom.yAxisTick ?? Default.YAxisTick, 32 | yAxisLine: custom.yAxisLine ?? Default.YAxisLine, 33 | series: custom.series ?? Default.Series, 34 | layout: custom.layout ?? Default.Layout, 35 | plot: custom.plot ?? Default.Plot, 36 | legend: custom.legend ?? Default.Legend, 37 | title: custom.title ?? Default.Title, 38 | dataLabel: custom.dataLabel ?? Default.DataLabel, 39 | grid: custom.grid ?? Default.Grid, 40 | gridXLine: custom.gridXLine ?? Default.GridXLine, 41 | gridYLine: custom.gridYLine ?? Default.GridYLine, 42 | }; 43 | 44 | const scale = getScale(data); 45 | 46 | return BubbleChartConfigProvider({ 47 | value: { 48 | custom: mergedConfig, 49 | data, 50 | scale, 51 | title, 52 | }, 53 | child: new Chart(), 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bubble-chart/types.ts: -------------------------------------------------------------------------------- 1 | import type { Widget } from "@meursyphus/flitter"; 2 | 3 | type ConfigArgs = (args: T, context: BubbleChartConfig) => Widget; 4 | 5 | export type BubbleChartData = { 6 | datasets: { 7 | legend: string; 8 | data: { 9 | x: number; 10 | y: number; 11 | value: number; 12 | label: string; 13 | }[]; 14 | }[]; 15 | }; 16 | 17 | export type BubbleScale = { 18 | min: number; 19 | max: number; 20 | step: number; 21 | } 22 | 23 | export type BubbleChartScale = { 24 | x: BubbleScale; 25 | y: BubbleScale; 26 | value: BubbleScale; 27 | }; 28 | 29 | export type BubbleChartCustom = { 30 | xAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 31 | yAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 32 | xAxisLabel: ConfigArgs<{ name: string; index: number }>; 33 | yAxisLabel: ConfigArgs<{ name: string; index: number }>; 34 | xAxisTick: ConfigArgs; 35 | yAxisTick: ConfigArgs; 36 | series: ConfigArgs<{ points: {x:number; y:number; value:number; label:string; legend:string; index:number}[]; scale: BubbleChartScale }>; 37 | bubble: ConfigArgs<{ value:number; label:string; legend:string; index:number }>; 38 | layout: ConfigArgs<{ title: Widget; legends: Widget[]; plot: Widget }>; 39 | plot: ConfigArgs<{ xAxis: Widget; yAxis: Widget; series: Widget; grid: Widget }>; 40 | legend: ConfigArgs<{ name: string; index: number }>; 41 | title: ConfigArgs<{ name: string }>; 42 | dataLabel: ConfigArgs<{ x:number; y:number; value: number; label: string; legend: string }>; 43 | xAxisLine: ConfigArgs; 44 | yAxisLine: ConfigArgs; 45 | grid: ConfigArgs<{ xLine: Widget; yLine: Widget }>; 46 | gridXLine: ConfigArgs; 47 | gridYLine: ConfigArgs; 48 | }; 49 | 50 | export type BubbleChartConfig = { 51 | custom: BubbleChartCustom; 52 | data: BubbleChartData; 53 | scale: BubbleChartScale; 54 | title: string; 55 | }; 56 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/scatter-chart/types.ts: -------------------------------------------------------------------------------- 1 | import type { Widget } from "@meursyphus/flitter"; 2 | 3 | type ConfigArgs = ( 4 | args: T, 5 | context: ScatterChartConfig, 6 | ) => Widget; 7 | 8 | export type ScatterChartData = { 9 | datasets: { 10 | legend: string; 11 | data: { 12 | x: number; 13 | y: number; 14 | label: string; 15 | }[]; 16 | }[]; 17 | }; 18 | 19 | export type ScatterScale = { 20 | min: number; 21 | max: number; 22 | step: number; 23 | }; 24 | 25 | export type ScatterChartScale = { 26 | x: ScatterScale; 27 | y: ScatterScale; 28 | }; 29 | 30 | export type ScatterChartCustom = { 31 | xAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 32 | yAxis: ConfigArgs<{ line: Widget; labels: Widget[]; tick: Widget }>; 33 | xAxisLabel: ConfigArgs<{ name: string; index: number }>; 34 | yAxisLabel: ConfigArgs<{ name: string; index: number }>; 35 | xAxisTick: ConfigArgs; 36 | yAxisTick: ConfigArgs; 37 | series: ConfigArgs<{ 38 | points: { 39 | x: number; 40 | y: number; 41 | label: string; 42 | legend: string; 43 | index: number; 44 | }[]; 45 | scale: ScatterChartScale; 46 | }>; 47 | scatter: ConfigArgs<{ 48 | label: string; 49 | legend: string; 50 | index: number; 51 | }>; 52 | layout: ConfigArgs<{ title: Widget; legends: Widget[]; plot: Widget }>; 53 | plot: ConfigArgs<{ 54 | xAxis: Widget; 55 | yAxis: Widget; 56 | series: Widget; 57 | grid: Widget; 58 | }>; 59 | legend: ConfigArgs<{ name: string; index: number }>; 60 | title: ConfigArgs<{ name: string }>; 61 | dataLabel: ConfigArgs<{ 62 | x: number; 63 | y: number; 64 | value: number; 65 | label: string; 66 | legend: string; 67 | }>; 68 | xAxisLine: ConfigArgs; 69 | yAxisLine: ConfigArgs; 70 | grid: ConfigArgs<{ xLine: Widget; yLine: Widget }>; 71 | gridXLine: ConfigArgs; 72 | gridYLine: ConfigArgs; 73 | }; 74 | 75 | export type ScatterChartConfig = { 76 | custom: ScatterChartCustom; 77 | data: ScatterChartData; 78 | scale: ScatterChartScale; 79 | title: string; 80 | }; 81 | -------------------------------------------------------------------------------- /packages/docs/src/content/docs/ko/01.getting-started/01.introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | nav_group: "시작하기" 3 | nav_order: 1 4 | title: "소개" 5 | description: "Headless Chart 라이브러리 소개 - 왜 Headless Chart인가?" 6 | --- 7 | 8 | # 소개 9 | 10 | ## 왜 Headless Chart인가? 11 | 12 | 차트를 만들 때 이런 고민을 해보신 적이 있나요? 13 | 14 | - "이 차트 라이브러리는 기능은 좋은데, 우리 디자인 시스템에 맞지 않아..." 15 | - "커스터마이징하려니 옵션이 너무 제한적이야" 16 | - "처음부터 만들자니 시간이 너무 오래 걸릴 것 같고..." 17 | 18 | **Headless Chart**는 바로 이런 문제를 해결하기 위해 만들어졌습니다. 19 | 20 | ## Headless가 뭔가요? 21 | 22 | "Headless"는 UI 없이 로직만 제공한다는 의미입니다. Headless Chart는: 23 | 24 | - ✅ **차트의 핵심 로직은 제공합니다** (데이터 계산, 스케일, 레이아웃) 25 | - ✅ **디자인은 여러분이 결정합니다** (색상, 모양, 애니메이션) 26 | - ✅ **완전한 커스터마이징이 가능합니다** (모든 컴포넌트를 교체 가능) 27 | 28 | ## Flitter 기반의 위젯 시스템 29 | 30 | Headless Chart는 [Flitter](https://github.com/meursyphus/flitter) 프레임워크를 기반으로 만들어졌습니다. Flitter는 Flutter의 위젯 시스템을 웹에서 구현한 프레임워크로, 다음과 같은 특징을 가집니다: 31 | 32 | - **선언적 UI**: 상태가 아닌 UI의 모습을 선언 33 | - **컴포지션 기반**: 작은 위젯들을 조합해 복잡한 UI 구성 34 | - **크로스 랜더러**: SVG와 Canvas 모두 지원 35 | 36 | ## 기존 차트 라이브러리와의 차이 37 | 38 | ### 일반적인 차트 라이브러리 39 | ```javascript 40 | // 옵션으로 커스터마이징 41 | new Chart({ 42 | type: 'bar', 43 | options: { 44 | color: 'blue', 45 | borderWidth: 2, 46 | // 제한된 옵션들... 47 | } 48 | }) 49 | ``` 50 | 51 | ### Headless Chart 52 | ```javascript 53 | // 컴포넌트를 직접 교체 54 | BarChart({ 55 | data: myData, 56 | custom: { 57 | bar: MyCustomBar, // 내가 만든 막대 58 | axis: MyCustomAxis, // 내가 만든 축 59 | legend: MyCustomLegend // 내가 만든 범례 60 | } 61 | }) 62 | ``` 63 | 64 | ## 주요 장점 65 | 66 | ### 1. 완전한 디자인 자유도 67 | 원하는 대로 모든 요소를 커스터마이징할 수 있습니다. 회사의 디자인 시스템에 완벽하게 맞출 수 있죠. 68 | 69 | ### 2. 점진적 커스터마이징 70 | 기본 제공되는 컴포넌트로 시작해서, 필요한 부분만 하나씩 교체할 수 있습니다. 71 | 72 | ### 3. 타입 안정성 73 | TypeScript로 작성되어 완벽한 타입 지원을 제공합니다. 74 | 75 | ### 4. 프레임워크 독립적 76 | React, Svelte, Vue 등 어떤 프레임워크에서도 사용할 수 있습니다. 77 | 78 | ## 이런 분들께 추천합니다 79 | 80 | - 🎨 **디자이너와 협업하는 개발자**: 디자인 시안을 100% 구현하고 싶은 분 81 | - 🏢 **디자인 시스템을 운영하는 팀**: 일관된 차트 컴포넌트가 필요한 분 82 | - 🚀 **새로운 차트 타입이 필요한 분**: 기존 라이브러리에 없는 차트를 만들고 싶은 분 83 | - 💡 **학습하고 싶은 분**: 차트가 어떻게 동작하는지 이해하고 싶은 분 84 | 85 | ## 다음 단계 86 | 87 | 이제 Headless Chart의 개념을 이해하셨다면, [설치](./02.installation.mdx) 가이드로 넘어가서 프로젝트에 설정해보세요! -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/heatmap-chart/index.ts: -------------------------------------------------------------------------------- 1 | // index.ts 2 | import { type Widget, StatelessWidget } from "@meursyphus/flitter"; 3 | import type { HeatmapCustom, HeatmapData, HeatmapScale } from "./types"; 4 | import { HeatmapConfigProvider } from "./provider"; 5 | import * as Default from "./default"; 6 | import Chart from "./chart"; 7 | import { classToFn } from "@utils/index"; 8 | 9 | class HeatmapChart extends StatelessWidget { 10 | #config: HeatmapCustom; 11 | #data: HeatmapData; 12 | #getScale: (data: HeatmapData) => HeatmapScale; 13 | #title: string; 14 | 15 | constructor({ 16 | custom: { 17 | xAxis = Default.XAxis, 18 | xAxisLabel = Default.XAxisLabel, 19 | xAxisTick = Default.XAxisTick, 20 | yAxis = Default.YAxis, 21 | yAxisLabel = Default.YAxisLabel, 22 | yAxisTick = Default.YAxisTick, 23 | heatmap = Default.Heatmap, 24 | segment = Default.Segment, 25 | layout = Default.Layout, 26 | plot = Default.Plot, 27 | title: titleWidget = Default.Title, 28 | xAxisLine = Default.XAxisLine, 29 | yAxisLine = Default.YAxisLine, 30 | } = {}, 31 | getScale = Default.getScale, 32 | data, 33 | title = "", 34 | }: { 35 | custom?: Partial; 36 | title?: string; 37 | data: HeatmapData; 38 | getScale?: (data: HeatmapData) => HeatmapScale; 39 | }) { 40 | super(); 41 | this.#data = data; 42 | this.#getScale = getScale; 43 | this.#title = title; 44 | this.#config = { 45 | layout, 46 | plot, 47 | xAxis, 48 | xAxisLabel, 49 | xAxisTick, 50 | xAxisLine, 51 | yAxis, 52 | yAxisLabel, 53 | yAxisTick, 54 | yAxisLine, 55 | heatmap, 56 | segment, 57 | title: titleWidget, 58 | }; 59 | } 60 | 61 | override build(): Widget { 62 | const scale = this.#getScale(this.#data); 63 | 64 | return HeatmapConfigProvider({ 65 | value: { 66 | custom: this.#config, 67 | data: this.#data, 68 | scale, 69 | title: this.#title, 70 | }, 71 | child: new Chart(), 72 | }); 73 | } 74 | } 75 | 76 | export default classToFn(HeatmapChart); 77 | -------------------------------------------------------------------------------- /packages/docs/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import type { MiddlewareHandler } from "astro"; 2 | import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "./i18n"; 3 | 4 | function detectLanguage( 5 | context: Parameters[0], 6 | ): (typeof SUPPORTED_LANGUAGES)[number] { 7 | const acceptLanguage = context.request.headers.get("accept-language"); 8 | 9 | if (!acceptLanguage) { 10 | return DEFAULT_LANGUAGE; 11 | } 12 | 13 | const langs = acceptLanguage.split(",").map((lang) => lang.split(";")[0]); 14 | for (const lang of langs) { 15 | const shortLang = lang.slice(0, 2).toLowerCase(); 16 | if ( 17 | SUPPORTED_LANGUAGES.includes( 18 | shortLang as (typeof SUPPORTED_LANGUAGES)[number], 19 | ) 20 | ) { 21 | return shortLang as (typeof SUPPORTED_LANGUAGES)[number]; 22 | } 23 | } 24 | 25 | return DEFAULT_LANGUAGE; 26 | } 27 | 28 | export const onRequest: MiddlewareHandler = async (context, next) => { 29 | const url = new URL(context.request.url); 30 | 31 | // Handle /docs routes 32 | if (url.pathname.startsWith("/docs")) { 33 | const pathSegments = url.pathname.split("/").filter(Boolean); 34 | 35 | // Handle exact /docs redirect 36 | if (pathSegments.length === 1 && pathSegments[0] === "docs") { 37 | const preferredLang = detectLanguage(context); 38 | return context.redirect(`/docs/${preferredLang}/getting-started/introduction`); 39 | } 40 | 41 | // Handle /docs/[lang] redirect (no specific page) 42 | if (pathSegments.length === 2) { 43 | const [, lang] = pathSegments; 44 | if (SUPPORTED_LANGUAGES.includes(lang as (typeof SUPPORTED_LANGUAGES)[number])) { 45 | return context.redirect(`/docs/${lang}/getting-started/introduction`); 46 | } 47 | } 48 | 49 | // Handle missing language in path 50 | if (pathSegments.length >= 2) { 51 | const [, possibleLang] = pathSegments; 52 | 53 | // If second segment is not a language, insert the detected language 54 | if (!SUPPORTED_LANGUAGES.includes(possibleLang as (typeof SUPPORTED_LANGUAGES)[number])) { 55 | const preferredLang = detectLanguage(context); 56 | const restPath = pathSegments.slice(1).join("/"); 57 | return context.redirect(`/docs/${preferredLang}/${restPath}`); 58 | } 59 | } 60 | } 61 | 62 | return next(); 63 | }; 64 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/line-chart/index.ts: -------------------------------------------------------------------------------- 1 | import { type Widget, StatelessWidget } from "@meursyphus/flitter"; 2 | import type { LineChartCustom, LineChartData, LineChartScale } from "./types"; 3 | import { LineChartConfigProvider } from "./provider"; 4 | import * as Default from "./default"; 5 | import Chart from "./chart"; 6 | import { classToFn } from "@utils/index"; 7 | 8 | class BarChart extends StatelessWidget { 9 | #config: LineChartCustom; 10 | #data: LineChartData; 11 | #getScale: (data: LineChartData) => LineChartScale; 12 | #title: string; 13 | 14 | constructor({ 15 | custom: { 16 | xAxis = Default.XAxis, 17 | xAxisLabel = Default.XAxisLabel, 18 | xAxisTick = Default.XAxisTick, 19 | yAxis = Default.YAxis, 20 | yAxisLabel = Default.YAxisLabel, 21 | yAxisTick = Default.YAxisTick, 22 | series = Default.Series, 23 | layout = Default.Layout, 24 | plot = Default.Plot, 25 | legend = Default.Legend, 26 | title: titleWidget = Default.Title, 27 | dataLabel = Default.DataLabel, 28 | line = Default.Line, 29 | xAxisLine = Default.XAxisLine, 30 | yAxisLine = Default.YAxisLine, 31 | grid = Default.Grid, 32 | gridXLine = Default.GridXLine, 33 | gridYLine = Default.GridYLine, 34 | } = {}, 35 | getScale = Default.getScale, 36 | data, 37 | title = "", 38 | }: { 39 | custom?: Partial; 40 | title?: string; 41 | data: LineChartData; 42 | getScale?: (data: LineChartData) => LineChartScale; 43 | }) { 44 | super(); 45 | this.#data = data; 46 | this.#getScale = getScale; 47 | this.#title = title; 48 | this.#config = { 49 | line, 50 | xAxis, 51 | xAxisLabel, 52 | xAxisTick, 53 | xAxisLine, 54 | yAxis, 55 | yAxisLabel, 56 | yAxisTick, 57 | yAxisLine, 58 | series, 59 | layout, 60 | plot, 61 | legend, 62 | title: titleWidget, 63 | dataLabel, 64 | grid, 65 | gridXLine, 66 | gridYLine, 67 | }; 68 | } 69 | 70 | override build(): Widget { 71 | const scale = this.#getScale(this.#data); 72 | 73 | return LineChartConfigProvider({ 74 | value: { 75 | custom: this.#config, 76 | data: this.#data, 77 | scale, 78 | title: this.#title, 79 | }, 80 | child: new Chart(), 81 | }); 82 | } 83 | } 84 | 85 | export default classToFn(BarChart); 86 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/area-chart/index.ts: -------------------------------------------------------------------------------- 1 | import { type Widget, StatelessWidget } from "@meursyphus/flitter"; 2 | import type { AreaChartCustom, AreaChartData, AreaChartScale } from "./types"; 3 | import { AreaChartConfigProvider } from "./provider"; 4 | import * as Default from "./default"; 5 | import Chart from "./chart"; 6 | import { classToFn } from "@utils/index"; 7 | 8 | class AreaChart extends StatelessWidget { 9 | #config: AreaChartCustom; 10 | #data: AreaChartData; 11 | #getScale: (data: AreaChartData) => AreaChartScale; 12 | #title: string; 13 | 14 | constructor({ 15 | custom: { 16 | xAxis = Default.XAxis, 17 | xAxisLabel = Default.XAxisLabel, 18 | xAxisTick = Default.XAxisTick, 19 | yAxis = Default.YAxis, 20 | yAxisLabel = Default.YAxisLabel, 21 | yAxisTick = Default.YAxisTick, 22 | series = Default.Series, 23 | layout = Default.Layout, 24 | plot = Default.Plot, 25 | legend = Default.Legend, 26 | title: titleWidget = Default.Title, 27 | dataLabel = Default.DataLabel, 28 | area: line = Default.Line, 29 | xAxisLine = Default.XAxisLine, 30 | yAxisLine = Default.YAxisLine, 31 | grid = Default.Grid, 32 | gridXLine = Default.GridXLine, 33 | gridYLine = Default.GridYLine, 34 | } = {}, 35 | getScale = Default.getScale, 36 | data, 37 | title = "", 38 | }: { 39 | custom?: Partial; 40 | title?: string; 41 | data: AreaChartData; 42 | getScale?: (data: AreaChartData) => AreaChartScale; 43 | }) { 44 | super(); 45 | this.#data = data; 46 | this.#getScale = getScale; 47 | this.#title = title; 48 | this.#config = { 49 | area: line, 50 | xAxis, 51 | xAxisLabel, 52 | xAxisTick, 53 | xAxisLine, 54 | yAxis, 55 | yAxisLabel, 56 | yAxisTick, 57 | yAxisLine, 58 | series, 59 | layout, 60 | plot, 61 | legend, 62 | title: titleWidget, 63 | dataLabel, 64 | grid, 65 | gridXLine, 66 | gridYLine, 67 | }; 68 | } 69 | 70 | override build(): Widget { 71 | const scale = this.#getScale(this.#data); 72 | 73 | return AreaChartConfigProvider({ 74 | value: { 75 | custom: this.#config, 76 | data: this.#data, 77 | scale, 78 | title: this.#title, 79 | }, 80 | child: new Chart(), 81 | }); 82 | } 83 | } 84 | 85 | export default classToFn(AreaChart); 86 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/utils/scale.ts: -------------------------------------------------------------------------------- 1 | const SNAP_VALUES = [1, 2, 5, 10]; 2 | 3 | export type ValueEdge = { 4 | min: number; 5 | max: number; 6 | }; 7 | 8 | export type Scale = { 9 | min: number; 10 | max: number; 11 | step: number; 12 | }; 13 | 14 | export function getValueEdge(values: number[]) { 15 | let min: number = Infinity; 16 | let max: number = -Infinity; 17 | values.forEach((value) => { 18 | min = Math.min(value, min); 19 | max = Math.max(value, max); 20 | }); 21 | 22 | return { 23 | min, 24 | max, 25 | }; 26 | } 27 | 28 | function getDigits(num: number): number { 29 | const logNumberDividedLN10 = 30 | num === 0 ? 1 : Math.log(Math.abs(num)) / Math.LN10; 31 | 32 | return 10 ** Math.floor(logNumberDividedLN10); 33 | } 34 | 35 | function getSnappedNumber(num: number): number { 36 | let snapNumber = 0; 37 | 38 | for (let i = 0, t = SNAP_VALUES.length; i < t; i += 1) { 39 | snapNumber = SNAP_VALUES[i]; 40 | const guideValue = (snapNumber + (SNAP_VALUES[i + 1] || snapNumber)) / 2; 41 | 42 | if (num <= guideValue) { 43 | break; 44 | } 45 | } 46 | 47 | return snapNumber; 48 | } 49 | 50 | function getNormalizedStep(stepSize: number) { 51 | const placeNumber = getDigits(stepSize); 52 | const simplifiedStepValue = stepSize / placeNumber; 53 | 54 | return getSnappedNumber(simplifiedStepValue) * placeNumber; 55 | } 56 | 57 | function getNormalizedLimit(limit: ValueEdge, stepSize: number): ValueEdge { 58 | let { min, max } = limit; 59 | const minNumber = Math.min(getDigits(max), getDigits(stepSize)); 60 | const placeNumber = minNumber > 1 ? 1 : 1 / minNumber; 61 | const fixedStep = stepSize * placeNumber; 62 | 63 | max = (Math.ceil((max * placeNumber) / fixedStep) * fixedStep) / placeNumber; 64 | 65 | if (min > stepSize) { 66 | min = 67 | (Math.floor((min * placeNumber) / fixedStep) * fixedStep) / placeNumber; 68 | } else if (min < 0) { 69 | min = 70 | -(Math.ceil((Math.abs(min) * placeNumber) / fixedStep) * fixedStep) / 71 | placeNumber; 72 | } else { 73 | min = 0; 74 | } 75 | 76 | return { min, max }; 77 | } 78 | 79 | export function refineScale(roughScale: Scale): Scale { 80 | const step = getNormalizedStep(roughScale.step); 81 | const edge = getNormalizedLimit( 82 | { min: roughScale.min, max: roughScale.max }, 83 | step 84 | ); 85 | 86 | return { 87 | max: edge.max, 88 | min: edge.min, 89 | step, 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /packages/docs/README.md: -------------------------------------------------------------------------------- 1 | # Astro Starter Kit: Basics 2 | 3 | ```sh 4 | npm create astro@latest -- --template basics 5 | ``` 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 8 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics) 9 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json) 10 | 11 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 12 | 13 | ![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554) 14 | 15 | ## 🚀 Project Structure 16 | 17 | Inside of your Astro project, you'll see the following folders and files: 18 | 19 | ```text 20 | / 21 | ├── public/ 22 | │ └── favicon.svg 23 | ├── src/ 24 | │ ├── components/ 25 | │ │ └── Card.astro 26 | │ ├── layouts/ 27 | │ │ └── Layout.astro 28 | │ └── pages/ 29 | │ └── index.astro 30 | └── package.json 31 | ``` 32 | 33 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 34 | 35 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 36 | 37 | Any static assets, like images, can be placed in the `public/` directory. 38 | 39 | ## 🧞 Commands 40 | 41 | All commands are run from the root of the project, from a terminal: 42 | 43 | | Command | Action | 44 | | :------------------------ | :----------------------------------------------- | 45 | | `npm install` | Installs dependencies | 46 | | `npm run dev` | Starts local dev server at `localhost:4321` | 47 | | `npm run build` | Build your production site to `./dist/` | 48 | | `npm run preview` | Preview your build locally, before deploying | 49 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 50 | | `npm run astro -- --help` | Get help using the Astro CLI | 51 | 52 | ## 👀 Want to learn more? 53 | 54 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 55 | -------------------------------------------------------------------------------- /packages/headless-chart/src/charts/bar-chart/index.ts: -------------------------------------------------------------------------------- 1 | import { type Widget, StatelessWidget } from '@meursyphus/flitter'; 2 | import type { BarChartCustom, BarChartData, BarChartScale } from './types'; 3 | import { BarChartConfigProvider } from './provider'; 4 | import * as Default from './default'; 5 | import Chart from './chart'; 6 | import { classToFn } from '@utils/index'; 7 | 8 | class BarChart extends StatelessWidget { 9 | #config: BarChartCustom; 10 | #data: BarChartData; 11 | #getScale: (data: BarChartData) => BarChartScale; 12 | #title: string; 13 | #direction: 'vertical' | 'horizontal'; 14 | 15 | constructor({ 16 | custom: { 17 | barGroup = Default.BarGroup, 18 | xAxis = Default.XAxis, 19 | xAxisLabel = Default.XAxisLabel, 20 | xAxisTick = Default.XAxisTick, 21 | yAxis = Default.YAxis, 22 | yAxisLabel = Default.YAxisLabel, 23 | yAxisTick = Default.YAxisTick, 24 | series = Default.Series, 25 | layout = Default.Layout, 26 | plot = Default.Plot, 27 | legend = Default.Legend, 28 | title: titleWidget = Default.Title, 29 | dataLabel = Default.DataLabel, 30 | bar = Default.Bar, 31 | xAxisLine = Default.XAxisLine, 32 | yAxisLine = Default.YAxisLine, 33 | grid = Default.Grid, 34 | gridXLine = Default.GridXLine, 35 | gridYLine = Default.GridYLine 36 | } = {}, 37 | getScale = Default.getScale, 38 | data, 39 | title = '', 40 | direction = 'vertical' 41 | }: { 42 | custom?: Partial; 43 | title?: string; 44 | data: BarChartData; 45 | direction?: 'vertical' | 'horizontal'; 46 | getScale?: (data: BarChartData) => BarChartScale; 47 | }) { 48 | super(); 49 | this.#data = data; 50 | this.#getScale = getScale; 51 | this.#title = title; 52 | this.#direction = direction; 53 | this.#config = { 54 | barGroup, 55 | bar, 56 | xAxis, 57 | xAxisLabel, 58 | xAxisTick, 59 | xAxisLine, 60 | yAxis, 61 | yAxisLabel, 62 | yAxisTick, 63 | yAxisLine, 64 | series, 65 | layout, 66 | plot, 67 | legend, 68 | title: titleWidget, 69 | dataLabel, 70 | grid, 71 | gridXLine, 72 | gridYLine 73 | }; 74 | } 75 | 76 | override build(): Widget { 77 | const scale = this.#getScale(this.#data); 78 | 79 | return BarChartConfigProvider({ 80 | value: { 81 | custom: this.#config, 82 | data: this.#data, 83 | scale, 84 | title: this.#title, 85 | direction: this.#direction 86 | }, 87 | child: new Chart() 88 | }); 89 | } 90 | } 91 | 92 | export default classToFn(BarChart); 93 | -------------------------------------------------------------------------------- /packages/docs/src/content/docs/en/01.getting-started/01.introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | nav_group: "Getting Started" 3 | nav_order: 1 4 | title: "Introduction" 5 | description: "Introduction to Headless Chart Library - Why Headless Chart?" 6 | --- 7 | 8 | # Introduction 9 | 10 | ## Why Headless Chart? 11 | 12 | Have you ever had these concerns when creating charts? 13 | 14 | - "This chart library has great features, but it doesn't match our design system..." 15 | - "The customization options are too limited" 16 | - "Building from scratch would take too much time..." 17 | 18 | **Headless Chart** was created to solve exactly these problems. 19 | 20 | ## What is Headless? 21 | 22 | "Headless" means providing only the logic without the UI. Headless Chart: 23 | 24 | - ✅ **Provides core chart logic** (data calculations, scales, layouts) 25 | - ✅ **You decide the design** (colors, shapes, animations) 26 | - ✅ **Fully customizable** (all components are replaceable) 27 | 28 | ## Widget System Based on Flitter 29 | 30 | Headless Chart is built on the [Flitter](https://github.com/meursyphus/flitter) framework. Flitter implements Flutter's widget system for the web with these characteristics: 31 | 32 | - **Declarative UI**: Declare the UI appearance rather than state 33 | - **Composition-based**: Build complex UI by composing small widgets 34 | - **Cross-renderer**: Supports both SVG and Canvas 35 | 36 | ## Difference from Traditional Chart Libraries 37 | 38 | ### Traditional Chart Libraries 39 | ```javascript 40 | // Customization through options 41 | new Chart({ 42 | type: 'bar', 43 | options: { 44 | color: 'blue', 45 | borderWidth: 2, 46 | // Limited options... 47 | } 48 | }) 49 | ``` 50 | 51 | ### Headless Chart 52 | ```javascript 53 | // Directly replace components 54 | BarChart({ 55 | data: myData, 56 | custom: { 57 | bar: MyCustomBar, // Your custom bar 58 | axis: MyCustomAxis, // Your custom axis 59 | legend: MyCustomLegend // Your custom legend 60 | } 61 | }) 62 | ``` 63 | 64 | ## Key Benefits 65 | 66 | ### 1. Complete Design Freedom 67 | Customize every element exactly as you want. Perfect for matching your company's design system. 68 | 69 | ### 2. Progressive Customization 70 | Start with default components and replace only what you need, one by one. 71 | 72 | ### 3. Type Safety 73 | Written in TypeScript with complete type support. 74 | 75 | ### 4. Framework Independent 76 | Works with React, Svelte, Vue, or any other framework. 77 | 78 | ## Recommended For 79 | 80 | - 🎨 **Developers working with designers**: Those who want to implement design specs 100% 81 | - 🏢 **Teams managing design systems**: Those who need consistent chart components 82 | - 🚀 **Those needing new chart types**: Those who want to create charts not available in existing libraries 83 | - 💡 **Those who want to learn**: Those who want to understand how charts work 84 | 85 | ## Next Steps 86 | 87 | Now that you understand the concept of Headless Chart, proceed to the [Installation](./02.installation.mdx) guide to set it up in your project! -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Headless Chart 2 | 3 | A powerful chart library built on top of the [Flitter](https://flitter.dev/) framework that provides a headless way to define and compose charts. Unlike traditional chart libraries where you are constrained by predefined components, headless-chart gives you building blocks that you can shape and style to meet your exact needs. 4 | 5 | ## Why Headless Chart? 6 | 7 | - **Full Control**: Instead of tweaking chart options, you directly modify widget structures using Flitter's widget system 8 | - **Framework Agnostic**: Works seamlessly with vanilla JavaScript/TypeScript or any UI framework 9 | - **Flexible Rendering**: Supports both SVG and Canvas rendering out of the box 10 | - **React Integration**: Easy integration with React through `@meursyphus/flitter-react` 11 | 12 | ## Quick Start 13 | 14 | ```bash 15 | # Create a new Vite project 16 | npm create vite@latest my-chart-app -- --template react 17 | cd my-chart-app 18 | 19 | # Install dependencies 20 | npm install @meursyphus/flitter @meursyphus/headless-chart @meursyphus/flitter-react 21 | ``` 22 | 23 | ### Basic Usage 24 | 25 | ```jsx 26 | import ReactWidget from "@meursyphus/flitter-react"; 27 | import { BarChart } from "@meursyphus/headless-chart"; 28 | 29 | export default function App() { 30 | const chart = BarChart({ 31 | data: { 32 | labels: ["January", "February", "March"], 33 | datasets: [ 34 | { legend: "Sales", values: [150, 200, 170] } 35 | ], 36 | title: "Monthly Sales" 37 | } 38 | }); 39 | 40 | return ( 41 | 47 | ); 48 | } 49 | ``` 50 | 51 | ### Custom Chart Example 52 | 53 | ```jsx 54 | import { BarChart } from "@meursyphus/headless-chart"; 55 | import { Text, Container, BoxDecoration } from "@meursyphus/flitter"; 56 | 57 | const customConfig = { 58 | bar: ({ value, label, legend }) => { 59 | return Container({ 60 | width: 20, 61 | height: value * 2, 62 | decoration: new BoxDecoration({ 63 | color: legend === "Sales" ? "blue" : "gray" 64 | }), 65 | child: Text(`${value}`, { 66 | style: { fill: "white" } 67 | }) 68 | }); 69 | }, 70 | title: ({ name }) => { 71 | return Text(name, { 72 | style: { fontSize: 18, fontWeight: "bold" } 73 | }); 74 | } 75 | }; 76 | 77 | const chart = BarChart({ 78 | data: { 79 | title: "Customized Sales Chart", 80 | labels: ["Jan", "Feb", "Mar"], 81 | datasets: [ 82 | { legend: "Sales", values: [150, 200, 170] } 83 | ] 84 | }, 85 | custom: customConfig 86 | }); 87 | ``` 88 | 89 | ## Documentation 90 | 91 | Visit our [documentation](https://headless-chart.codeium.com/docs/getting-started/introduction) to learn more about: 92 | - Detailed installation guides 93 | - Framework integrations 94 | - Chart customization 95 | - Advanced examples 96 | 97 | ## Examples 98 | 99 | Check out our [interactive examples](https://headless-chart.codeium.com/charts) to see what you can build with headless-chart. 100 | 101 | ## License 102 | 103 | MIT 104 | -------------------------------------------------------------------------------- /packages/headless-chart/README.md: -------------------------------------------------------------------------------- 1 | # Headless Chart 2 | 3 | A powerful chart library built on top of the [Flitter](https://flitter.dev) framework that provides a headless way to define and compose charts. Unlike traditional chart libraries where you are constrained by predefined components, headless-chart gives you building blocks that you can shape and style to meet your exact needs. 4 | 5 | ## Why Headless Chart? 6 | 7 | - **Full Control**: Instead of tweaking chart options, you directly modify widget structures using Flitter's widget system 8 | - **Framework Agnostic**: Works seamlessly with vanilla JavaScript/TypeScript or any UI framework 9 | - **Flexible Rendering**: Supports both SVG and Canvas rendering out of the box 10 | - **React Integration**: Easy integration with React through `@meursyphus/flitter-react` 11 | 12 | ## Quick Start 13 | 14 | ```bash 15 | # Create a new Vite project 16 | npm create vite@latest my-chart-app -- --template react 17 | cd my-chart-app 18 | 19 | # Install dependencies 20 | npm install @meursyphus/flitter @meursyphus/headless-chart @meursyphus/flitter-react 21 | ``` 22 | 23 | ### Basic Usage 24 | 25 | ```jsx 26 | import ReactWidget from "@meursyphus/flitter-react"; 27 | import { BarChart } from "@meursyphus/headless-chart"; 28 | 29 | export default function App() { 30 | const chart = BarChart({ 31 | data: { 32 | labels: ["January", "February", "March"], 33 | datasets: [ 34 | { legend: "Sales", values: [150, 200, 170] } 35 | ], 36 | }, 37 | title: "Monthly Sales" 38 | }); 39 | 40 | return ( 41 | 47 | ); 48 | } 49 | ``` 50 | 51 | ### Custom Chart Example 52 | 53 | ```jsx 54 | import { BarChart } from "@meursyphus/headless-chart"; 55 | import { Text, Container, BoxDecoration } from "@meursyphus/flitter"; 56 | 57 | const customConfig = { 58 | bar: ({ value, label, legend }) => { 59 | return Container({ 60 | width: 20, 61 | height: value * 2, 62 | decoration: new BoxDecoration({ 63 | color: legend === "Sales" ? "blue" : "gray" 64 | }), 65 | child: Text(`${value}`, { 66 | style: { fill: "white" } 67 | }) 68 | }); 69 | }, 70 | title: ({ name }) => { 71 | return Text(name, { 72 | style: { fontSize: 18, fontWeight: "bold" } 73 | }); 74 | } 75 | }; 76 | 77 | const chart = BarChart({ 78 | data: { 79 | labels: ["Jan", "Feb", "Mar"], 80 | datasets: [ 81 | { legend: "Sales", values: [150, 200, 170] } 82 | ] 83 | }, 84 | title: "Customized Sales Chart", 85 | custom: customConfig 86 | }); 87 | ``` 88 | 89 | ## Documentation 90 | 91 | Visit our [documentation](https://headless-chart.codeium.com/docs/getting-started/introduction) to learn more about: 92 | - Detailed installation guides 93 | - Framework integrations 94 | - Chart customization 95 | - Advanced examples 96 | 97 | ## Examples 98 | 99 | Check out our [interactive examples](https://headless-chart.codeium.com/charts) to see what you can build with headless-chart. 100 | 101 | ## License 102 | 103 | MIT 104 | -------------------------------------------------------------------------------- /packages/docs/src/pages/charts/TableOfContents.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getCollection } from "astro:content"; 3 | 4 | function resolveSlug(slug: string) { 5 | const segments = slug.split("/"); 6 | const cleanedSegments = segments.map((segment) => 7 | segment.replace(/^\d+\.?/, ""), 8 | ); 9 | return cleanedSegments.join("/"); 10 | } 11 | 12 | const charts = await getCollection("charts"); 13 | const sortedCharts = charts 14 | .sort((a, b) => a.slug.localeCompare(b.slug)) 15 | .map((entry) => ({ ...entry, 16 | slug: resolveSlug(entry.slug), 17 | })) 18 | .map((entry) => { 19 | const segments = entry.slug.split("/"); 20 | return { 21 | ...entry, 22 | slug: segments.map((segment) => segment.replace(/^\d+./, "")).join("/"), 23 | order: parseInt(segments[1].split("_")[0]) || 0, 24 | nav_group: entry.data.nav_group || "Others", 25 | }; 26 | }); 27 | 28 | // Group tutorials by nav_group and filter by language 29 | const { currentSlug } = Astro.props; 30 | 31 | const groupedTutorials = sortedCharts.reduce( 32 | (acc, tutorial) => { 33 | const group = tutorial.nav_group; 34 | if (!acc[group]) { 35 | acc[group] = []; 36 | } 37 | acc[group].push(tutorial); 38 | return acc; 39 | }, 40 | {} as Record, 41 | ); 42 | 43 | 44 | --- 45 | 46 |
47 | { 48 | Object.entries(groupedTutorials).map(([group, tutorials]) => ( 49 |
50 |

51 | {group} 52 |

53 | 68 |
69 | )) 70 | } 71 |
72 | 73 | 123 | -------------------------------------------------------------------------------- /packages/headless-chart/src/shared/cartesian/getScale.ts: -------------------------------------------------------------------------------- 1 | import type { CartesianData, CartesianScale } from "./types"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 4 | export function getScale({ 5 | datasets, 6 | }: Omit): CartesianScale { 7 | const valueEdge = getValueEdge(datasets.map(({ values }) => values)); 8 | 9 | const roughEdge = { 10 | min: valueEdge.min > 0 ? 0 : valueEdge.min, 11 | max: valueEdge.max < 0 ? 0 : valueEdge.max, 12 | }; 13 | const roughStepCount = 10; 14 | 15 | const roughScale: Scale = { 16 | min: roughEdge.min, 17 | max: roughEdge.max, 18 | step: (roughEdge.max - roughEdge.min) / roughStepCount, 19 | }; 20 | 21 | return refineScale(roughScale); 22 | } 23 | 24 | function getValueEdge(valuesByLegend: number[][]) { 25 | let min: number = Infinity; 26 | let max: number = -Infinity; 27 | valuesByLegend.forEach((values) => { 28 | values.forEach((value) => { 29 | min = Math.min(value, min); 30 | max = Math.max(value, max); 31 | }); 32 | }); 33 | 34 | return { 35 | min, 36 | max, 37 | }; 38 | } 39 | const SNAP_VALUES = [1, 2, 5, 10]; 40 | 41 | type ValueEdge = { 42 | min: number; 43 | max: number; 44 | }; 45 | 46 | export type Scale = { 47 | min: number; 48 | max: number; 49 | step: number; 50 | }; 51 | 52 | function getDigits(num: number): number { 53 | const logNumberDividedLN10 = 54 | num === 0 ? 1 : Math.log(Math.abs(num)) / Math.LN10; 55 | 56 | return 10 ** Math.floor(logNumberDividedLN10); 57 | } 58 | 59 | function getSnappedNumber(num: number): number { 60 | let snapNumber = 0; 61 | 62 | for (let i = 0, t = SNAP_VALUES.length; i < t; i += 1) { 63 | snapNumber = SNAP_VALUES[i]; 64 | const guideValue = (snapNumber + (SNAP_VALUES[i + 1] || snapNumber)) / 2; 65 | 66 | if (num <= guideValue) { 67 | break; 68 | } 69 | } 70 | 71 | return snapNumber; 72 | } 73 | 74 | function getNormalizedStep(stepSize: number) { 75 | const placeNumber = getDigits(stepSize); 76 | const simplifiedStepValue = stepSize / placeNumber; 77 | 78 | return getSnappedNumber(simplifiedStepValue) * placeNumber; 79 | } 80 | 81 | /** 82 | * Get normalized limit values 83 | * max = 155 and step = 10 ---> max = 160 84 | */ 85 | function getNormalizedLimit(limit: ValueEdge, stepSize: number): ValueEdge { 86 | let { min, max } = limit; 87 | const minNumber = Math.min(getDigits(max), getDigits(stepSize)); 88 | const placeNumber = minNumber > 1 ? 1 : 1 / minNumber; 89 | const fixedStep = stepSize * placeNumber; 90 | 91 | // ceil max value step digits 92 | max = (Math.ceil((max * placeNumber) / fixedStep) * fixedStep) / placeNumber; 93 | 94 | if (min > stepSize) { 95 | // floor min value to multiples of step 96 | min = 97 | (Math.floor((min * placeNumber) / fixedStep) * fixedStep) / placeNumber; 98 | } else if (min < 0) { 99 | min = 100 | -(Math.ceil((Math.abs(min) * placeNumber) / fixedStep) * fixedStep) / 101 | placeNumber; 102 | } else { 103 | min = 0; 104 | } 105 | 106 | return { min, max }; 107 | } 108 | 109 | function refineScale(roughScaleDate: Scale): Scale { 110 | const step = getNormalizedStep(roughScaleDate.step); 111 | const edge = getNormalizedLimit( 112 | { min: roughScaleDate.min, max: roughScaleDate.max }, 113 | step, 114 | ); 115 | 116 | return { 117 | min: edge.min, 118 | max: edge.max, 119 | step, 120 | }; 121 | } 122 | -------------------------------------------------------------------------------- /docs-guide.md: -------------------------------------------------------------------------------- 1 | # Headless Chart 한국어 문서 가이드 2 | 3 | ## 📚 제안하는 문서 구조 4 | 5 | ### 1. 소개 (Introduction) 6 | - **Headless Chart란?** 7 | - Headless 개념 설명 8 | - 기존 차트 라이브러리와의 차이점 9 | - 주요 특징 및 장점 10 | - **Flitter 프레임워크 소개** 11 | - Flutter 스타일 위젯 시스템 12 | - 선언적 UI 패러다임 13 | - Headless Chart와의 관계 14 | 15 | ### 2. 시작하기 (Getting Started) 16 | - **설치 및 설정** 17 | - npm/yarn 설치 18 | - TypeScript 설정 19 | - 번들러 설정 (Vite, Webpack) 20 | - **첫 번째 차트** 21 | - 기본 Bar Chart 예제 22 | - 필수 구성 요소 설명 23 | - 렌더링 과정 이해 24 | 25 | ### 3. 핵심 개념 (Core Concepts) 26 | - **위젯 시스템** 27 | - StatelessWidget 기본 28 | - build 메서드 패턴 29 | - 컨텍스트와 프로바이더 30 | - **컴포넌트 구조** 31 | - Layout, Plot, Axes 이해 32 | - 기본 컴포넌트와 커스텀 컴포넌트 33 | - 컴포넌트 교체 방법 34 | - **데이터 구조** 35 | - labels와 datasets 36 | - 데이터 포맷 규칙 37 | - 다중 데이터셋 처리 38 | 39 | ### 4. 차트 타입별 가이드 (Chart Types) 40 | - **Bar Chart** 41 | - 수직/수평 막대 차트 42 | - 그룹/스택 막대 차트 43 | - 애니메이션 효과 44 | - **Line Chart** 45 | - 기본 라인 차트 46 | - 다중 라인 차트 47 | - 곡선 옵션 (smooth, step) 48 | - **Area Chart** 49 | - 기본 영역 차트 50 | - 스택 영역 차트 51 | - 그라데이션 효과 52 | - **Scatter Chart** 53 | - 산점도 기본 54 | - 버블 크기 조절 55 | - 커스텀 포인트 모양 56 | - **Bubble Chart** 57 | - 3차원 데이터 표현 58 | - 크기 스케일 조정 59 | - 색상 매핑 60 | - **Heatmap Chart** 61 | - 그리드 기반 시각화 62 | - 색상 스케일 설정 63 | - 셀 커스터마이징 64 | 65 | ### 5. 커스터마이징 (Customization) 66 | - **컴포넌트 교체** 67 | - custom prop 활용법 68 | - 커스텀 축 만들기 69 | - 커스텀 레전드 구현 70 | - **스타일링** 71 | - 색상 테마 설정 72 | - 폰트 및 텍스트 스타일 73 | - 반응형 디자인 74 | - **애니메이션** 75 | - 진입 애니메이션 76 | - 호버 효과 77 | - 트랜지션 효과 78 | 79 | ### 6. 고급 기능 (Advanced Features) 80 | - **스케일 함수** 81 | - 선형/로그 스케일 82 | - 커스텀 스케일 구현 83 | - 도메인/레인지 설정 84 | - **이벤트 처리** 85 | - 클릭/호버 이벤트 86 | - 툴팁 구현 87 | - 인터랙티브 기능 88 | - **성능 최적화** 89 | - 대용량 데이터 처리 90 | - 메모이제이션 전략 91 | - 렌더링 최적화 92 | 93 | ### 7. 통합 가이드 (Integration) 94 | - **React 통합** 95 | - @meursyphus/flitter-react 사용법 96 | - 상태 관리 연동 97 | - 컴포넌트 래핑 98 | - **Svelte 통합** 99 | - Svelte 어댑터 사용 100 | - 반응성 연결 101 | - 생명주기 관리 102 | - **Next.js/Nuxt 통합** 103 | - SSR 고려사항 104 | - 동적 임포트 105 | - 하이드레이션 처리 106 | 107 | ### 8. 레시피 (Recipes) 108 | - **실전 예제** 109 | - 대시보드 차트 110 | - 실시간 데이터 업데이트 111 | - 복합 차트 구성 112 | - **마이그레이션 가이드** 113 | - Chart.js에서 마이그레이션 114 | - D3.js 코드 전환 115 | - 기타 라이브러리 비교 116 | - **일반적인 패턴** 117 | - 데이터 전처리 118 | - 에러 처리 119 | - 접근성 개선 120 | 121 | ### 9. API 레퍼런스 (API Reference) 122 | - **차트 컴포넌트 API** 123 | - Props 상세 설명 124 | - 타입 정의 125 | - 기본값 및 옵션 126 | - **유틸리티 함수** 127 | - classToFn 128 | - 스케일 헬퍼 129 | - 데이터 변환 도구 130 | - **프로바이더 시스템** 131 | - ConfigProvider 132 | - 컨텍스트 API 133 | - 설정 상속 규칙 134 | 135 | ### 10. 문제 해결 (Troubleshooting) 136 | - **자주 묻는 질문** 137 | - **일반적인 오류와 해결법** 138 | - **디버깅 팁** 139 | - **성능 문제 진단** 140 | 141 | ## 🎯 작성 우선순위 142 | 143 | ### Phase 1 (핵심 문서) 144 | 1. Flitter 프레임워크 소개 145 | 2. 핵심 개념 (위젯 시스템, 컴포넌트 구조) 146 | 3. 모든 차트 타입 기본 가이드 147 | 148 | ### Phase 2 (실용 문서) 149 | 1. 커스터마이징 가이드 150 | 2. React 통합 상세 가이드 151 | 3. 실전 예제 (레시피) 152 | 153 | ### Phase 3 (참조 문서) 154 | 1. API 레퍼런스 155 | 2. 고급 기능 156 | 3. 문제 해결 가이드 157 | 158 | ## 📝 문서 작성 가이드라인 159 | 160 | 1. **코드 예제 중심**: 모든 개념은 실행 가능한 코드와 함께 161 | 2. **점진적 설명**: 간단한 예제에서 복잡한 예제로 162 | 3. **시각적 결과**: 가능한 모든 예제에 결과 이미지 포함 163 | 4. **인터랙티브 데모**: Sandpack을 활용한 실시간 편집 164 | 5. **실무 관점**: 실제 사용 사례 기반 설명 165 | 166 | ## 🔗 참고 자료 활용 167 | 168 | - Flitter AI.md 문서의 위젯 시스템 설명 169 | - 기존 영문 문서의 구조와 예제 170 | - Chart.js, Recharts 등 인기 라이브러리의 문서 구조 171 | - 한국 개발자 커뮤니티의 피드백 -------------------------------------------------------------------------------- /packages/docs/src/pages/charts/SandPack.tsx: -------------------------------------------------------------------------------- 1 | import Editor from "@monaco-editor/react"; 2 | import { 3 | useActiveCode, 4 | SandpackStack, 5 | FileTabs, 6 | useSandpack, 7 | SandpackProvider, 8 | SandpackLayout, 9 | SandpackPreview, 10 | } from "@codesandbox/sandpack-react"; 11 | 12 | import { useEffect, useRef, useCallback } from "react"; 13 | 14 | function useDebounce any>( 15 | callback: T, 16 | delay: number, 17 | ): T { 18 | const timer = useRef(null); 19 | 20 | const debouncedCallback = useCallback( 21 | (...args: Parameters) => { 22 | if (timer.current) { 23 | clearTimeout(timer.current); 24 | } 25 | timer.current = setTimeout(() => { 26 | callback(...args); 27 | }, delay); 28 | }, 29 | [callback, delay], 30 | ) as T; 31 | 32 | return debouncedCallback; 33 | } 34 | 35 | function MonacoEditor() { 36 | const { code, updateCode } = useActiveCode(); 37 | const { sandpack, dispatch } = useSandpack(); 38 | const refresh = useDebounce(() => dispatch({ type: "refresh" }), 1000); 39 | 40 | const handleChange = (value: string | undefined) => { 41 | updateCode(value ?? ""); 42 | if (sandpack.activeFile !== "/App.js") { 43 | refresh(); 44 | } 45 | }; 46 | 47 | const editorRef = useRef(); 48 | 49 | useEffect(() => { 50 | const handleResize = () => { 51 | if (editorRef.current == null) return; 52 | editorRef.current.layout({}); 53 | console.log(editorRef.current.layout); 54 | }; 55 | window.addEventListener("resize", handleResize); 56 | return () => window.removeEventListener("resize", handleResize); 57 | }, []); 58 | 59 | return ( 60 | 61 | 62 |
63 | { 72 | editorRef.current = editor; 73 | // 초기 레이아웃 설정 74 | editor.layout(); 75 | }} 76 | options={{ 77 | minimap: { enabled: false }, 78 | scrollBeyondLastLine: false, 79 | padding: { top: 8, bottom: 8 }, 80 | automaticLayout: true, // 자동 레이아웃 조정 활성화 81 | wordWrap: "on" // 긴 줄 자동 줄바꿈 82 | }} 83 | /> 84 |
85 |
86 | ); 87 | } 88 | 89 | const customSetup = { 90 | dependencies: { 91 | "@meursyphus/flitter-react": "0.0.8", 92 | "@meursyphus/flitter": "2.0.2", 93 | "react-dom": "^18.2.0", 94 | react: "^18.2.0", 95 | "@meursyphus/headless-chart": "^0.0.8", 96 | }, 97 | }; 98 | 99 | export default function MySandpack({ 100 | files, 101 | }: Readonly<{ 102 | files: Record; 103 | }>) { 104 | return ( 105 | 116 | 117 | 118 | 119 | 120 | 121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Common Development Commands 6 | 7 | ### Root Level (Workspace) 8 | ```bash 9 | npm run dev # Start documentation site development server 10 | npm run build # Build the headless-chart library 11 | npm run docs:build # Build documentation site (includes library pre-build) 12 | ``` 13 | 14 | ### Headless Chart Library (`packages/headless-chart`) 15 | ```bash 16 | npm run dev # Start Vite development server 17 | npm run build # TypeScript compile + Vite build 18 | npm run lint # Run ESLint with TypeScript support 19 | npm run preview # Preview built library 20 | ``` 21 | 22 | ### Documentation Site (`packages/docs`) 23 | ```bash 24 | npm run dev # Start Astro development server 25 | npm run build # Type check + Astro build 26 | npm run preview # Preview production build 27 | ``` 28 | 29 | ## Architecture Overview 30 | 31 | Headless Chart is a widget-based chart library built on the Flitter framework, providing a "headless" approach where developers get building blocks instead of predefined components. 32 | 33 | ### Key Architectural Patterns 34 | 35 | 1. **Widget-Based System**: All charts are composed of nested StatelessWidget classes from Flitter. Each widget implements a `build(context)` method. 36 | 37 | 2. **Provider Pattern**: Configuration flows through context providers (e.g., `BarChartConfigProvider`). This allows deep customization at any level. 38 | 39 | 3. **Modular Chart Components**: Each chart is composed of replaceable parts: 40 | - Layout, Plot, Axes (X/Y), Labels, Ticks, Grid, Legend, Title, Data Labels 41 | - Default implementations in `default/` folders can be overridden via `custom` prop 42 | 43 | 4. **Chart Creation Pattern**: 44 | ```typescript 45 | BarChart({ 46 | data: { labels, datasets, title }, 47 | custom: { /* override any component */ }, 48 | getScale: /* custom scale function */, 49 | direction: 'vertical' | 'horizontal' 50 | }) 51 | ``` 52 | 53 | 5. **Shared Components**: Common Cartesian chart components are in `/shared/cartesian` to avoid duplication across bar, line, area, scatter charts. 54 | 55 | ### Code Organization 56 | 57 | Each chart type follows this structure: 58 | ``` 59 | packages/headless-chart/src/charts/[chart-type]/ 60 | ├── index.ts # Main export using classToFn utility 61 | ├── chart.ts # Core chart widget class 62 | ├── provider.ts # Configuration provider 63 | ├── types.ts # TypeScript type definitions 64 | └── default/ # Default component implementations 65 | ``` 66 | 67 | ### Important Technical Details 68 | 69 | - **No Traditional Dependencies**: Built from scratch on Flitter, no D3.js or Chart.js 70 | - **Framework Integration**: Primary support for React via `@meursyphus/flitter-react`, secondary for Svelte 71 | - **TypeScript Path Aliases**: Use `@shared/*` and `@utils/*` imports 72 | - **Export Pattern**: Classes are converted to functions via `classToFn` utility for better DX 73 | - **Configuration**: Uses functional configuration approach with `ConfigArgs` type 74 | 75 | ### Development Workflow 76 | 77 | 1. When modifying charts, ensure changes work in both library and documentation 78 | 2. Test changes by running `npm run dev` at root to see live updates in docs 79 | 3. Always run `npm run lint` in the library package before committing 80 | 4. Chart components should follow the established widget pattern with proper TypeScript types 81 | 5. Documentation examples should be interactive using Sandpack when possible 82 | 83 | ### Git Conventions 84 | 85 | **Important**: All commit messages and pull request descriptions must be written in English. This ensures consistency and accessibility for the international development community. -------------------------------------------------------------------------------- /packages/docs/src/components/ui/Header.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const url = new URL(Astro.request.url); 3 | const pathname = url.pathname; 4 | 5 | let current: "docs" | "charts" | "i18n" | "none" = pathname.startsWith("/docs") 6 | ? "docs" 7 | : pathname.startsWith("/charts") 8 | ? "charts" 9 | : pathname.startsWith("/i18n") 10 | ? "i18n" 11 | : "none"; 12 | --- 13 | 14 |
15 |
16 | 17 | logo 18 | Headless Chart 19 | 20 | 21 | 35 | 36 | 44 |
45 |
46 | 47 | -------------------------------------------------------------------------------- /packages/docs/src/pages/docs/SideBar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getCollection } from "astro:content"; 3 | import { normalizePath } from "./utils"; 4 | 5 | function resolveSlug(slug: string) { 6 | const segments = slug.split("/"); 7 | const cleanedSegments = segments.map((segment) => 8 | segment.replace(/^\d+\.?/, ""), 9 | ); 10 | return cleanedSegments.join("/"); 11 | } 12 | 13 | const _docsEntries = await getCollection("docs"); 14 | const docsEntries = _docsEntries 15 | .sort((a, b) => a.slug.localeCompare(b.slug)) 16 | .map((entry) => ({ 17 | ...entry, 18 | slug: resolveSlug(entry.slug), 19 | })); 20 | 21 | 22 | 23 | const url = new URL(Astro.request.url); 24 | const pathSegments = url.pathname.split("/").filter(Boolean); 25 | const lang = pathSegments[1]; 26 | 27 | 28 | // 현재 언어에 맞는 문서만 필터링 29 | const filteredDocs = docsEntries.filter((entry) => 30 | entry.slug.startsWith(lang + "/"), 31 | ) 32 | 33 | // navigation 정보로 표현할 객체 만들기 34 | const navigation = filteredDocs.reduce( 35 | (acc, entry, i) => { 36 | const { nav_group, nav_order, title, nav_title } = entry.data; 37 | 38 | // 해당 navGroup이 이미 존재하는지 확인 39 | let group = acc.find((g) => g.name === nav_group); 40 | if (!group) { 41 | // 존재하지 않으면 새로운 그룹 생성 42 | group = { name: nav_group, items: [] }; 43 | acc.push(group); 44 | } 45 | 46 | // 그룹에 문서 추가 47 | group.items.push({ 48 | url: `/docs/${entry.slug}`, 49 | title: nav_title ?? title, 50 | }); 51 | 52 | return acc; 53 | }, 54 | [] as { 55 | name: string; 56 | items: { url: string; title: string }[]; 57 | }[], 58 | ); 59 | 60 | const normalizedPathname = normalizePath(url.pathname); 61 | --- 62 | 63 | 87 | 88 | -------------------------------------------------------------------------------- /packages/docs/src/content/docs/ko/01.getting-started/02.installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | nav_group: "시작하기" 3 | nav_order: 2 4 | title: "설치" 5 | description: "Headless Chart 설치 및 기본 설정 가이드" 6 | --- 7 | 8 | # 설치 9 | 10 | ## 패키지 설치 11 | 12 | Headless Chart는 npm 또는 yarn을 통해 설치할 수 있습니다. 13 | 14 | ```bash 15 | # npm 사용 16 | npm install @meursyphus/headless-chart 17 | 18 | # yarn 사용 19 | yarn add @meursyphus/headless-chart 20 | 21 | # pnpm 사용 22 | pnpm add @meursyphus/headless-chart 23 | ``` 24 | 25 | ## React 프로젝트 설정 26 | 27 | React에서 사용하려면 `@meursyphus/flitter-react` 패키지도 함께 설치해야 합니다. 28 | 29 | ```bash 30 | npm install @meursyphus/headless-chart @meursyphus/flitter-react 31 | ``` 32 | 33 | ### 기본 설정 34 | 35 | ```typescript 36 | // React 프로젝트 37 | import { Widget } from '@meursyphus/flitter-react'; 38 | import { BarChart } from '@meursyphus/headless-chart'; 39 | 40 | function App() { 41 | const chart = BarChart({ 42 | data: { 43 | labels: ['1월', '2월', '3월'], 44 | datasets: [{ 45 | label: '매출', 46 | data: [100, 200, 150] 47 | }] 48 | } 49 | }); 50 | 51 | return ; 52 | } 53 | ``` 54 | 55 | ## Svelte 프로젝트 설정 56 | 57 | Svelte에서는 `@meursyphus/flitter-svelte` 패키지를 사용합니다. 58 | 59 | ```bash 60 | npm install @meursyphus/headless-chart @meursyphus/flitter-svelte 61 | ``` 62 | 63 | ### 기본 설정 64 | 65 | ```svelte 66 | 80 | 81 | 82 | ``` 83 | 84 | ## TypeScript 설정 85 | 86 | Headless Chart는 TypeScript로 작성되어 완벽한 타입 지원을 제공합니다. 87 | 88 | ### tsconfig.json 권장 설정 89 | 90 | ```json 91 | { 92 | "compilerOptions": { 93 | "target": "ES2020", 94 | "module": "ESNext", 95 | "moduleResolution": "bundler", 96 | "strict": true, 97 | "esModuleInterop": true, 98 | "skipLibCheck": true, 99 | "forceConsistentCasingInFileNames": true 100 | } 101 | } 102 | ``` 103 | 104 | ### 타입 정의 활용 105 | 106 | ```typescript 107 | import type { ChartData, CustomCartesianChart } from '@meursyphus/headless-chart'; 108 | 109 | // 데이터 타입 110 | const data: ChartData = { 111 | labels: ['1월', '2월', '3월'], 112 | datasets: [{ 113 | label: '매출', 114 | data: [100, 200, 150] 115 | }] 116 | }; 117 | 118 | // 커스텀 컴포넌트 타입 119 | const customComponents: CustomCartesianChart = { 120 | bar: MyCustomBar, 121 | axis: MyCustomAxis 122 | }; 123 | ``` 124 | 125 | ## 번들러 설정 126 | 127 | ### Vite 128 | 129 | Vite는 별도의 설정 없이 바로 사용할 수 있습니다. 130 | 131 | ```javascript 132 | // vite.config.js 133 | export default { 134 | // 특별한 설정 필요 없음 135 | } 136 | ``` 137 | 138 | ### Webpack 139 | 140 | Webpack 5를 사용하는 경우: 141 | 142 | ```javascript 143 | // webpack.config.js 144 | module.exports = { 145 | module: { 146 | rules: [ 147 | { 148 | test: /\.(js|jsx|ts|tsx)$/, 149 | exclude: /node_modules/, 150 | use: { 151 | loader: 'babel-loader', 152 | options: { 153 | presets: [ 154 | '@babel/preset-env', 155 | '@babel/preset-react', 156 | '@babel/preset-typescript' 157 | ] 158 | } 159 | } 160 | } 161 | ] 162 | }, 163 | resolve: { 164 | extensions: ['.js', '.jsx', '.ts', '.tsx'] 165 | } 166 | }; 167 | ``` 168 | 169 | ## 렌더러 선택 170 | 171 | Headless Chart는 SVG와 Canvas 두 가지 렌더러를 지원합니다. 172 | 173 | ### SVG 렌더러 (기본값) 174 | 175 | ```jsx 176 | 180 | ``` 181 | 182 | **SVG 렌더러 장점:** 183 | - 벡터 기반으로 확대해도 선명함 184 | - CSS로 스타일링 가능 185 | - DOM 요소로 접근 가능 186 | 187 | ### Canvas 렌더러 188 | 189 | ```jsx 190 | 194 | ``` 195 | 196 | **Canvas 렌더러 장점:** 197 | - 대용량 데이터 처리에 유리 198 | - 더 나은 애니메이션 성능 199 | - 메모리 사용량 적음 200 | 201 | ## 개발 환경 설정 202 | 203 | ### ESLint 설정 204 | 205 | ```json 206 | { 207 | "extends": [ 208 | "eslint:recommended", 209 | "plugin:@typescript-eslint/recommended" 210 | ], 211 | "rules": { 212 | // 프로젝트에 맞는 규칙 설정 213 | } 214 | } 215 | ``` 216 | 217 | ### Prettier 설정 218 | 219 | ```json 220 | { 221 | "semi": true, 222 | "trailingComma": "es5", 223 | "singleQuote": true, 224 | "printWidth": 80, 225 | "tabWidth": 2 226 | } 227 | ``` 228 | 229 | ## 문제 해결 230 | 231 | ### 모듈을 찾을 수 없는 경우 232 | 233 | ```bash 234 | # node_modules 재설치 235 | rm -rf node_modules package-lock.json 236 | npm install 237 | ``` 238 | 239 | ### TypeScript 타입 오류 240 | 241 | ```bash 242 | # TypeScript 버전 확인 243 | npx tsc --version 244 | 245 | # 최신 버전으로 업데이트 246 | npm install typescript@latest -D 247 | ``` 248 | 249 | ## 다음 단계 250 | 251 | 설치가 완료되었다면 [빠른 시작](./03.quick-start.mdx) 가이드에서 첫 번째 차트를 만들어보세요! -------------------------------------------------------------------------------- /packages/docs/src/pages/docs/Toc.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { MarkdownHeading } from "astro"; 3 | 4 | interface Props { 5 | headings: MarkdownHeading[]; 6 | } 7 | 8 | const { headings } = Astro.props; 9 | --- 10 | 11 |
12 |

On this page

13 | 35 |
36 | 37 | 95 | 96 | -------------------------------------------------------------------------------- /packages/docs/src/layouts/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Header from "../components/ui/Header.astro"; 3 | interface Props { 4 | title?: string; 5 | description?: string; 6 | image?: string; 7 | } 8 | 9 | const { 10 | image = "/og.png", 11 | title = "Headless Charts - 😫 Tired of Chart Limitations? 🎯", 12 | description = "✨ Copy, Paste, Done! 🎨 Ultimate Freedom in Chart Customization 🚀 Zero Restrictions", 13 | } = Astro.props; 14 | 15 | const BASE_ORIGIN = Astro.url.origin; 16 | const fullCanonicalUrl = `${BASE_ORIGIN}${Astro.url.pathname}`; 17 | 18 | --- 19 | 20 | 21 | 22 | 23 | 24 | 25 | {title} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 47 | 48 | 49 |
50 | 51 |
52 | 53 |
54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /packages/docs/src/content/docs/en/01.getting-started/02.installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | nav_group: "Getting Started" 3 | nav_order: 2 4 | title: "Installation" 5 | description: "Headless Chart installation and basic setup guide" 6 | --- 7 | 8 | # Installation 9 | 10 | ## Package Installation 11 | 12 | Headless Chart can be installed via npm or yarn. 13 | 14 | ```bash 15 | # Using npm 16 | npm install @meursyphus/headless-chart 17 | 18 | # Using yarn 19 | yarn add @meursyphus/headless-chart 20 | 21 | # Using pnpm 22 | pnpm add @meursyphus/headless-chart 23 | ``` 24 | 25 | ## React Project Setup 26 | 27 | For React usage, you need to install the `@meursyphus/flitter-react` package as well. 28 | 29 | ```bash 30 | npm install @meursyphus/headless-chart @meursyphus/flitter-react 31 | ``` 32 | 33 | ### Basic Setup 34 | 35 | ```typescript 36 | // React project 37 | import { Widget } from '@meursyphus/flitter-react'; 38 | import { BarChart } from '@meursyphus/headless-chart'; 39 | 40 | function App() { 41 | const chart = BarChart({ 42 | data: { 43 | labels: ['Jan', 'Feb', 'Mar'], 44 | datasets: [{ 45 | label: 'Sales', 46 | data: [100, 200, 150] 47 | }] 48 | } 49 | }); 50 | 51 | return ; 52 | } 53 | ``` 54 | 55 | ## Svelte Project Setup 56 | 57 | For Svelte, use the `@meursyphus/flitter-svelte` package. 58 | 59 | ```bash 60 | npm install @meursyphus/headless-chart @meursyphus/flitter-svelte 61 | ``` 62 | 63 | ### Basic Setup 64 | 65 | ```svelte 66 | 80 | 81 | 82 | ``` 83 | 84 | ## TypeScript Setup 85 | 86 | Headless Chart is written in TypeScript and provides complete type support. 87 | 88 | ### Recommended tsconfig.json 89 | 90 | ```json 91 | { 92 | "compilerOptions": { 93 | "target": "ES2020", 94 | "module": "ESNext", 95 | "moduleResolution": "bundler", 96 | "strict": true, 97 | "esModuleInterop": true, 98 | "skipLibCheck": true, 99 | "forceConsistentCasingInFileNames": true 100 | } 101 | } 102 | ``` 103 | 104 | ### Using Type Definitions 105 | 106 | ```typescript 107 | import type { ChartData, CustomCartesianChart } from '@meursyphus/headless-chart'; 108 | 109 | // Data types 110 | const data: ChartData = { 111 | labels: ['Jan', 'Feb', 'Mar'], 112 | datasets: [{ 113 | label: 'Sales', 114 | data: [100, 200, 150] 115 | }] 116 | }; 117 | 118 | // Custom component types 119 | const customComponents: CustomCartesianChart = { 120 | bar: MyCustomBar, 121 | axis: MyCustomAxis 122 | }; 123 | ``` 124 | 125 | ## Bundler Configuration 126 | 127 | ### Vite 128 | 129 | Vite works out of the box without any special configuration. 130 | 131 | ```javascript 132 | // vite.config.js 133 | export default { 134 | // No special configuration needed 135 | } 136 | ``` 137 | 138 | ### Webpack 139 | 140 | For Webpack 5: 141 | 142 | ```javascript 143 | // webpack.config.js 144 | module.exports = { 145 | module: { 146 | rules: [ 147 | { 148 | test: /\.(js|jsx|ts|tsx)$/, 149 | exclude: /node_modules/, 150 | use: { 151 | loader: 'babel-loader', 152 | options: { 153 | presets: [ 154 | '@babel/preset-env', 155 | '@babel/preset-react', 156 | '@babel/preset-typescript' 157 | ] 158 | } 159 | } 160 | } 161 | ] 162 | }, 163 | resolve: { 164 | extensions: ['.js', '.jsx', '.ts', '.tsx'] 165 | } 166 | }; 167 | ``` 168 | 169 | ## Renderer Selection 170 | 171 | Headless Chart supports both SVG and Canvas renderers. 172 | 173 | ### SVG Renderer (Default) 174 | 175 | ```jsx 176 | 180 | ``` 181 | 182 | **SVG Renderer Advantages:** 183 | - Vector-based, stays crisp when scaled 184 | - Can be styled with CSS 185 | - Accessible as DOM elements 186 | 187 | ### Canvas Renderer 188 | 189 | ```jsx 190 | 194 | ``` 195 | 196 | **Canvas Renderer Advantages:** 197 | - Better for large datasets 198 | - Better animation performance 199 | - Lower memory usage 200 | 201 | ## Development Environment Setup 202 | 203 | ### ESLint Configuration 204 | 205 | ```json 206 | { 207 | "extends": [ 208 | "eslint:recommended", 209 | "plugin:@typescript-eslint/recommended" 210 | ], 211 | "rules": { 212 | // Configure rules for your project 213 | } 214 | } 215 | ``` 216 | 217 | ### Prettier Configuration 218 | 219 | ```json 220 | { 221 | "semi": true, 222 | "trailingComma": "es5", 223 | "singleQuote": true, 224 | "printWidth": 80, 225 | "tabWidth": 2 226 | } 227 | ``` 228 | 229 | ## Troubleshooting 230 | 231 | ### Module Not Found 232 | 233 | ```bash 234 | # Reinstall node_modules 235 | rm -rf node_modules package-lock.json 236 | npm install 237 | ``` 238 | 239 | ### TypeScript Type Errors 240 | 241 | ```bash 242 | # Check TypeScript version 243 | npx tsc --version 244 | 245 | # Update to latest version 246 | npm install typescript@latest -D 247 | ``` 248 | 249 | ## Next Steps 250 | 251 | Once installation is complete, check out the [Quick Start](./03.quick-start.mdx) guide to create your first chart! -------------------------------------------------------------------------------- /packages/docs/src/components/ui/BarChart.tsx: -------------------------------------------------------------------------------- 1 | import ReactWidget from "@meursyphus/flitter-react"; 2 | import { BarChart as HeadlessBarChart} from "@meursyphus/headless-chart"; 3 | import { 4 | Text, 5 | Border, 6 | BorderSide, 7 | BoxDecoration, 8 | Column, 9 | Container, 10 | EdgeInsets, 11 | Row, 12 | SizedBox, 13 | TextStyle, 14 | Padding, 15 | MainAxisSize, 16 | MainAxisAlignment, 17 | Stack, 18 | Positioned 19 | } from "@meursyphus/flitter"; 20 | import {useEffect, useState} from 'react'; 21 | const BarChart =() => { 22 | 23 | const [barChart, setBarChart] = useState(); 24 | 25 | const data = { 26 | labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 27 | datasets:[ 28 | { 29 | legend: "온도", 30 | values: [2, 5, 11, 19, 24, 27, 30, 30, 26, 20, 12, 5], 31 | }, 32 | ], 33 | }; 34 | 35 | const backgroundColors = [ 36 | "rgba(255, 99, 132, 0.2)", 37 | "rgba(255, 159, 64, 0.2)", 38 | "rgba(255, 205, 86, 0.2)", 39 | "rgba(75, 192, 192, 0.2)", 40 | "rgba(54, 162, 235, 0.2)", 41 | "rgba(153, 102, 255, 0.2)", 42 | "rgba(201, 203, 207, 0.2)", 43 | ]; 44 | 45 | const borderColors = [ 46 | "rgb(255, 99, 132)", 47 | "rgb(255, 159, 64)", 48 | "rgb(255, 205, 86)", 49 | "rgb(75, 192, 192)", 50 | "rgb(54, 162, 235)", 51 | "rgb(153, 102, 255)", 52 | "rgb(201, 203, 207)", 53 | ]; 54 | 55 | const chartInstance = HeadlessBarChart({ 56 | data, 57 | custom: { 58 | layout: (...[{ legends, plot }]) => 59 | Container({ 60 | padding: EdgeInsets.only({ left: 30, bottom: 70 }), 61 | child: Stack({ 62 | children: [ 63 | Positioned({ 64 | top: 0, 65 | right: 0, 66 | child: Text("Inspired by Chart.js", { 67 | style: new TextStyle({ 68 | fontSize: 14, 69 | color: "#999999", 70 | fontFamily: "Noto Sans JP", 71 | }), 72 | }), 73 | }), 74 | Column({ 75 | children: [ 76 | Row({ 77 | mainAxisAlignment: MainAxisAlignment.center, 78 | children: [...legends, SizedBox({ width: 30 })], 79 | }), 80 | SizedBox({ height: 5 }), 81 | plot, 82 | ], 83 | }), 84 | ], 85 | }), 86 | }), 87 | bar: (...[{ label }]) => { 88 | const index = data.labels.indexOf(label); 89 | const backgroundColor = backgroundColors[index]; 90 | const borderSide = new BorderSide({ 91 | color: borderColors[index], 92 | width: 1, 93 | }); 94 | return Container({ 95 | width: Infinity, 96 | margin: EdgeInsets.symmetric({ horizontal: 8 }), 97 | height: Infinity, 98 | decoration: new BoxDecoration({ 99 | color: backgroundColor, 100 | border: new Border({ 101 | left: borderSide, 102 | right: borderSide, 103 | top: borderSide, 104 | }), 105 | }), 106 | }); 107 | }, 108 | xAxisLabel: (...[{ name }]) => Padding({ 109 | padding: EdgeInsets.only({ top: 1 }), 110 | child: Text(name, { 111 | style: new TextStyle({ 112 | fontFamily: "Noto Sans JP", 113 | fontSize: 12, 114 | color: "#666666", 115 | }), 116 | }), 117 | }), 118 | yAxisLabel: (...[{ name }]) => Padding({ 119 | padding: EdgeInsets.only({ right: 1 }), 120 | child: Text(name, { 121 | style: new TextStyle({ 122 | fontFamily: "Noto Sans JP", 123 | fontSize: 12, 124 | color: "#666666", 125 | }), 126 | }), 127 | }), 128 | xAxisTick: () => Container({ 129 | height: 6, 130 | width: 1, 131 | color: "#DDDDDD", 132 | }), 133 | yAxisTick: () => Container({ 134 | height: 1, 135 | width: 6, 136 | color: "#DDDDDD", 137 | }), 138 | yAxisLine: () => Container({ 139 | color: "#BBBBBB", 140 | width: 1, 141 | height: Infinity, 142 | }), 143 | xAxisLine: () => Container({ 144 | color: "#BBBBBB", 145 | height: 1, 146 | width: Infinity, 147 | }), 148 | legend: (...[{ name }]) => Row({ 149 | mainAxisAlignment: MainAxisAlignment.center, 150 | mainAxisSize: MainAxisSize.min, 151 | children: [ 152 | Container({ 153 | width: 36, 154 | height: 12, 155 | decoration: new BoxDecoration({ 156 | color: "rgba(255, 99, 132, 0.2)", 157 | border: Border.all({ color: "rgb(255, 99, 132)" }), 158 | }), 159 | }), 160 | SizedBox({ width: 5 }), 161 | Text(name, { 162 | style: new TextStyle({ 163 | fontFamily: "Noto Sans JP", 164 | fontSize: 14, 165 | color: "#666666", 166 | }), 167 | }), 168 | ], 169 | }), 170 | gridXLine: () => Container({ height: 1, color: "#EEEEEE" }), 171 | gridYLine: () => Container({ width: 1, color: "#EEEEEE" }), 172 | }, 173 | }); 174 | 175 | useEffect(() => { 176 | setBarChart(chartInstance); 177 | },[]) 178 | 179 | return ( 180 |
181 |

Headless 막대 차트

182 | {barChart && ( 183 | 189 | )} 190 |
191 | ); 192 | } 193 | 194 | export default BarChart; 195 | -------------------------------------------------------------------------------- /packages/docs/src/content/docs/ko/02.core-concepts/01.flitter-basics.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | nav_group: "핵심 개념" 3 | nav_order: 1 4 | title: "Flitter 기초" 5 | description: "Headless Chart의 기반이 되는 Flitter 프레임워크 이해하기" 6 | --- 7 | 8 | # Flitter 기초 9 | 10 | Headless Chart는 Flitter 프레임워크 위에 구축되었습니다. Flitter를 이해하면 차트를 자유자재로 커스터마이징할 수 있습니다. 11 | 12 | ## Flitter란? 13 | 14 | Flitter는 Flutter의 위젯 시스템을 웹에서 구현한 JavaScript 렌더링 엔진입니다. Flutter의 강력한 UI 구성 방식을 웹에서도 사용할 수 있게 해줍니다. 15 | 16 | ### 주요 특징 17 | 18 | - **선언적 UI**: "어떻게"가 아닌 "무엇을" 그릴지 선언 19 | - **위젯 트리**: 작은 위젯들을 조합해 복잡한 UI 구성 20 | - **크로스 렌더러**: SVG와 Canvas 모두 지원 21 | - **자동 레이아웃**: Flutter의 레이아웃 알고리즘 그대로 구현 22 | 23 | ## 핵심 개념 24 | 25 | ### 1. 위젯(Widget) 26 | 27 | 모든 UI 요소는 위젯입니다. 위젯은 UI의 일부를 선언적으로 설명합니다. 28 | 29 | ```javascript 30 | // 간단한 컨테이너 위젯 31 | Container({ 32 | width: 200, 33 | height: 100, 34 | color: '#3b82f6' 35 | }) 36 | 37 | // 텍스트 위젯 38 | Text('안녕하세요', { 39 | style: new TextStyle({ 40 | fontSize: 16, 41 | color: '#1f2937' 42 | }) 43 | }) 44 | ``` 45 | 46 | ### 2. 위젯 트리 47 | 48 | 위젯들은 트리 구조로 조합됩니다. 부모 위젯은 자식 위젯을 포함합니다. 49 | 50 | ```javascript 51 | Container({ 52 | child: Column({ 53 | children: [ 54 | Text('제목'), 55 | Text('내용'), 56 | Container({ height: 50, color: '#f3f4f6' }) 57 | ] 58 | }) 59 | }) 60 | ``` 61 | 62 | ### 3. 레이아웃 시스템 63 | 64 | Flitter는 Flutter와 동일한 레이아웃 제약 시스템을 사용합니다. 65 | 66 | - **제약은 아래로 전달됩니다** (Constraints go down) 67 | - **크기는 위로 전달됩니다** (Sizes go up) 68 | - **부모가 위치를 결정합니다** (Parent sets position) 69 | 70 | ## 중요한 문법 규칙 71 | 72 | ### 1. Import 규칙 73 | 74 | ```javascript 75 | // ✅ 올바른 방법 76 | import Widget from '@meursyphus/flitter-react'; // 기본 내보내기 77 | import { Container, Text, Row } from '@meursyphus/flitter'; // 명명된 내보내기 78 | 79 | // ❌ 잘못된 방법 80 | import { Widget } from '@meursyphus/flitter-react'; // 에러! 81 | ``` 82 | 83 | ### 2. 위젯 vs 타입 클래스 84 | 85 | ```javascript 86 | // 위젯: new 키워드 없이 사용 87 | Container({ width: 100 }) 88 | Text('Hello') 89 | Row({ children: [] }) 90 | 91 | // 타입 클래스: new 키워드 필수 92 | new TextStyle({ fontSize: 16 }) 93 | new BoxDecoration({ color: '#FF0000' }) 94 | new EdgeInsets.all(10) 95 | ``` 96 | 97 | ### 3. 색상 표현 98 | 99 | ```javascript 100 | // ✅ 올바른 색상 표현 101 | color: '#FF0000' // HEX 문자열 102 | color: 'rgba(255, 0, 0, 0.5)' // RGBA 문자열 103 | color: 'red' // 색상 이름 104 | 105 | // ❌ 잘못된 색상 표현 106 | color: 0xFFFF0000 // Flutter 스타일 숫자는 사용 불가 107 | ``` 108 | 109 | ## 기본 위젯 소개 110 | 111 | ### Container 112 | 113 | 가장 기본적인 레이아웃 위젯입니다. 114 | 115 | ```javascript 116 | Container({ 117 | width: 200, 118 | height: 100, 119 | padding: EdgeInsets.all(16), 120 | margin: EdgeInsets.symmetric({ horizontal: 8 }), 121 | decoration: new BoxDecoration({ 122 | color: '#ffffff', 123 | borderRadius: BorderRadius.circular(8), 124 | border: Border.all({ color: '#e5e7eb', width: 1 }) 125 | }), 126 | child: Text('컨테이너 안의 텍스트') 127 | }) 128 | ``` 129 | 130 | ### Row & Column 131 | 132 | 수평/수직으로 자식들을 배치합니다. 133 | 134 | ```javascript 135 | // 수평 배치 136 | Row({ 137 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 138 | crossAxisAlignment: CrossAxisAlignment.center, 139 | children: [ 140 | Text('왼쪽'), 141 | Text('중간'), 142 | Text('오른쪽') 143 | ] 144 | }) 145 | 146 | // 수직 배치 147 | Column({ 148 | mainAxisAlignment: MainAxisAlignment.start, 149 | crossAxisAlignment: CrossAxisAlignment.stretch, 150 | children: [ 151 | Text('상단'), 152 | SizedBox({ height: 20 }), // 간격 153 | Text('하단') 154 | ] 155 | }) 156 | ``` 157 | 158 | ### Stack & Positioned 159 | 160 | 위젯들을 겹쳐서 배치합니다. 161 | 162 | ```javascript 163 | Stack({ 164 | children: [ 165 | Container({ width: 200, height: 200, color: '#3b82f6' }), 166 | Positioned({ 167 | top: 10, 168 | right: 10, 169 | child: Text('우측 상단') 170 | }), 171 | Positioned({ 172 | bottom: 10, 173 | left: 10, 174 | child: Text('좌측 하단') 175 | }) 176 | ] 177 | }) 178 | ``` 179 | 180 | ## 실제 차트 예제 181 | 182 | Flitter 개념을 차트에 적용해보겠습니다. 183 | 184 | ```javascript 185 | // 간단한 막대 차트 레이아웃 186 | Container({ 187 | padding: EdgeInsets.all(20), 188 | child: Row({ 189 | crossAxisAlignment: CrossAxisAlignment.end, 190 | children: [ 191 | // 첫 번째 막대 192 | Container({ 193 | width: 50, 194 | height: 100, 195 | color: '#3b82f6', 196 | margin: EdgeInsets.symmetric({ horizontal: 5 }) 197 | }), 198 | // 두 번째 막대 199 | Container({ 200 | width: 50, 201 | height: 150, 202 | color: '#10b981', 203 | margin: EdgeInsets.symmetric({ horizontal: 5 }) 204 | }), 205 | // 세 번째 막대 206 | Container({ 207 | width: 50, 208 | height: 80, 209 | color: '#f59e0b', 210 | margin: EdgeInsets.symmetric({ horizontal: 5 }) 211 | }) 212 | ] 213 | }) 214 | }) 215 | ``` 216 | 217 | ## 스타일링 218 | 219 | ### TextStyle 220 | 221 | 텍스트의 모양을 정의합니다. 222 | 223 | ```javascript 224 | Text('스타일이 적용된 텍스트', { 225 | style: new TextStyle({ 226 | fontSize: 18, 227 | fontWeight: 'bold', 228 | color: '#1f2937', 229 | fontFamily: 'Pretendard', 230 | letterSpacing: -0.5 231 | }) 232 | }) 233 | ``` 234 | 235 | ### BoxDecoration 236 | 237 | 컨테이너의 장식을 정의합니다. 238 | 239 | ```javascript 240 | Container({ 241 | decoration: new BoxDecoration({ 242 | color: '#ffffff', 243 | borderRadius: BorderRadius.circular(12), 244 | boxShadow: [{ 245 | color: 'rgba(0, 0, 0, 0.1)', 246 | blurRadius: 10, 247 | offset: { x: 0, y: 4 } 248 | }], 249 | gradient: { 250 | type: 'linear', 251 | colors: ['#3b82f6', '#8b5cf6'], 252 | begin: { x: 0, y: 0 }, 253 | end: { x: 1, y: 1 } 254 | } 255 | }) 256 | }) 257 | ``` 258 | 259 | ## 다음 단계 260 | 261 | 이제 Flitter의 기본을 이해했으니, [위젯 시스템](./02.widget-system.mdx)에서 더 깊이 있는 내용을 학습해보세요. 262 | 263 | 차트를 만들 때 이런 Flitter 지식이 어떻게 활용되는지 [차트 가이드](../03.chart-guide/01.bar-chart.mdx)에서 확인할 수 있습니다. -------------------------------------------------------------------------------- /packages/docs/src/content/charts/01.bar/01.chartjs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | nav_group: "Bar" 3 | nav_order: 1 4 | title: "Chartjs" 5 | description: "A quick example of a headless-chart inspired by chart.js" 6 | image: /bar-chart/chartjs.png 7 | files: 8 | App.js: | 9 | import Widget from "@meursyphus/flitter-react"; 10 | import { BarChart } from "@meursyphus/headless-chart"; 11 | import { 12 | Text, 13 | Border, 14 | BorderSide, 15 | BoxDecoration, 16 | Column, 17 | Container, 18 | EdgeInsets, 19 | Row, 20 | SizedBox, 21 | TextStyle, 22 | Padding, 23 | MainAxisSize, 24 | MainAxisAlignment, 25 | Stack, 26 | Positioned 27 | } from "@meursyphus/flitter"; 28 | 29 | const data = { 30 | labels: ["January", "February", "March", "April", "May", "June", "July"], 31 | datasets: [ 32 | { 33 | legend: "My First Dataset", 34 | values: [65, 59, 80, 81, 56, 55, 40], 35 | }, 36 | ], 37 | }; 38 | 39 | const backgroundColors = [ 40 | "rgba(255, 99, 132, 0.2)", 41 | "rgba(255, 159, 64, 0.2)", 42 | "rgba(255, 205, 86, 0.2)", 43 | "rgba(75, 192, 192, 0.2)", 44 | "rgba(54, 162, 235, 0.2)", 45 | "rgba(153, 102, 255, 0.2)", 46 | "rgba(201, 203, 207, 0.2)", 47 | ]; 48 | const borderColors = [ 49 | "rgb(255, 99, 132)", 50 | "rgb(255, 159, 64)", 51 | "rgb(255, 205, 86)", 52 | "rgb(75, 192, 192)", 53 | "rgb(54, 162, 235)", 54 | "rgb(153, 102, 255)", 55 | "rgb(201, 203, 207)", 56 | ]; 57 | 58 | const chart = BarChart({ 59 | data, 60 | custom: { 61 | layout: (...[{ legends, plot }]) => 62 | Container({ 63 | padding: EdgeInsets.only({ left: 30, bottom: 70 }), 64 | child: Stack({ 65 | children: [ 66 | Positioned({ 67 | top: 0, 68 | right: 0, 69 | child: Text("Inspired by Chart.js", { 70 | style: new TextStyle({ 71 | fontSize: 14, 72 | color: "#999999", 73 | fontFamily: "Noto Sans JP", 74 | }), 75 | }), 76 | }), 77 | Column({ 78 | children: [ 79 | Row({ 80 | mainAxisAlignment: MainAxisAlignment.center, 81 | children: [...legends, SizedBox({ width: 30 })], 82 | }), 83 | SizedBox({ height: 5 }), 84 | plot, 85 | ], 86 | }), 87 | ], 88 | }), 89 | }), 90 | bar: (...[{ label }]) => { 91 | const index = data.labels.indexOf(label); 92 | const backgroundColor = backgroundColors[index]; 93 | const borderSide = new BorderSide({ 94 | color: borderColors[index], 95 | width: 1, 96 | }); 97 | return Container({ 98 | width: Infinity, 99 | margin: EdgeInsets.symmetric({ horizontal: 8 }), 100 | height: Infinity, 101 | decoration: new BoxDecoration({ 102 | color: backgroundColor, 103 | border: new Border({ 104 | left: borderSide, 105 | right: borderSide, 106 | top: borderSide, 107 | }), 108 | }), 109 | }); 110 | }, 111 | xAxisLabel: (...[{ name }]) => Padding({ 112 | padding: EdgeInsets.only({ top: 1 }), 113 | child: Text(name, { 114 | style: new TextStyle({ 115 | fontFamily: "Noto Sans JP", 116 | fontSize: 12, 117 | color: "#666666", 118 | }), 119 | }), 120 | }), 121 | yAxisLabel: (...[{ name }]) => Padding({ 122 | padding: EdgeInsets.only({ right: 1 }), 123 | child: Text(name, { 124 | style: new TextStyle({ 125 | fontFamily: "Noto Sans JP", 126 | fontSize: 12, 127 | color: "#666666", 128 | }), 129 | }), 130 | }), 131 | xAxisTick: () => Container({ 132 | height: 6, 133 | width: 1, 134 | color: "#DDDDDD", 135 | }), 136 | yAxisTick: () => Container({ 137 | height: 1, 138 | width: 6, 139 | color: "#DDDDDD", 140 | }), 141 | yAxisLine: () => Container({ 142 | color: "#BBBBBB", 143 | width: 1, 144 | height: Infinity, 145 | }), 146 | xAxisLine: () => Container({ 147 | color: "#BBBBBB", 148 | height: 1, 149 | width: Infinity, 150 | }), 151 | legend: (...[{ name }]) => Row({ 152 | mainAxisAlignment: MainAxisAlignment.center, 153 | mainAxisSize: MainAxisSize.min, 154 | children: [ 155 | Container({ 156 | width: 36, 157 | height: 12, 158 | decoration: new BoxDecoration({ 159 | color: "rgba(255, 99, 132, 0.2)", 160 | border: Border.all({ color: "rgb(255, 99, 132)" }), 161 | }), 162 | }), 163 | SizedBox({ width: 5 }), 164 | Text(name, { 165 | style: new TextStyle({ 166 | fontFamily: "Noto Sans JP", 167 | fontSize: 14, 168 | color: "#666666", 169 | }), 170 | }), 171 | ], 172 | }), 173 | gridXLine: () => Container({ height: 1, color: "#EEEEEE" }), 174 | gridYLine: () => Container({ width: 1, color: "#EEEEEE" }), 175 | }, 176 | }); 177 | 178 | export default function App() { 179 | return ( 180 | 186 | ); 187 | } 188 | --- 189 | 190 | # Chartjs Inspired 191 | 192 | A very simple headless-chart example inspired by Chart.js. All customizations are done inline for simplicity. 193 | --------------------------------------------------------------------------------