រស្មី · blog

ការបម្លែងតារាង Markdown ទៅជាក្រាហ្វិកនៅលើគេហទំព័រ Astro

ប្រធានបទវិធីធ្វើ​

ខ្ញុំ​ប្រើប្រាស់ Obsidian ដើម្បី​សរសេរ​កំណត់ត្រា និង​អត្ថបទ​ផ្សេង។​ ហើយ​នៅក្នុង Obsidian មានមុខ​ងារ​ដ៏​ល្អ​ក្នុង​ការ​បម្លែង​តារាង​ធម្មតា​ទៅជា​ក្រាហ្វិក។ ខ្ញុំ​ចង់បាន​ភាព​ងាយស្រួល​ដូចគ្នា​នេះ​នៅ​លើ​វេ​ប​សាយ​នេះ ដូច្នេះ​ខ្ញុំ​បាន​សាកល្បង​កូ​ពីអ​ត្ថ​បទ markdown ពី Obsidian ចូល​ទៅ​ក្នុង​វេ​ប​សាយ​នេះ​តែម្តង តែ​គ្រប់​ក្រាប​ទាំងអស់​បាន​បង្ហាញ​ចេញ​ជា​អត្ថបទ​ធម្មតា!

មូលហេតុ​គឺជា​រឿង​ដែល​គួរ​ស្វែងយល់ ព្រោះ​វា​ជា​អ្នក​កំណត់​ទម្រង់​នៃ​ដំណោះស្រាយ​ទាំងមូល។ ទាំងនេះ​គឺជា Obsidian plugins។ ពួកវា​សរសេរ​កំណត់ត្រា​របស់​អ្នក​ឡើងវិញ​នៅពេល​អ្នក​កំពុង​មើល​វា​នៅក្នុង Obsidian។ គេហទំព័រ​ប្លុក​របស់ខ្ញុំ​ត្រូវ​បាន​បង្កើត​ឡើង​ដោយ Astro ដែល compile Markdown ជាមួយ pipeline ស្ដ​ង់​ដារ remark/rehype ដែល​ស្គាល់ CommonMark និង plugin ទាំងឡាយ​ណា​ដែល​អ្នក​បាន​ចុះឈ្មោះ​យ៉ាង​ច្បាស់លាស់។ Syntax របស់ Obsidian មិនមែន​ជាផ្នែក​នៃ​រឿង​ទាំងនោះ​ឡើយ។

ដូច្នេះ​គោលដៅ​បាន​ក្លាយជា៖ សរសេរ​តារាង Markdown ធម្មតា បន្ថែម​មួយ​ជួរ រួច​ទទួល​បាន​ក្រាហ្វិក​ពិតប្រាកដ — ដោយ​មិន​ចាំបាច់​ទាញ​យក framework មក​ប្រើ​ឡើយ។

ដំបូង ខ្ញុំ​បាន​ពិនិត្យមើល​ថា​តើមាន​បណ្ណាល័យ (library) ណា​ដែល​បាន​ធ្វើ​រឿង​នេះ​រួចហើយ​ឬនៅ

មុន​ពេល​សរសេរ​អ្វីមួយ ខ្ញុំ​បាន​ស្វែងរក​នៅ​លើ npm។ បេក្ខភាព​ដែល​លេចធ្លោ​ជាងគេ​បាន​បរាជ័យ​លើ​តម្រូវការ​ទាំងពីរ៖

  • turn-to-chart — ឈ្មោះ​គឺ​ត្រូវគ្នា​យ៉ាង​ល្អឥតខ្ចោះ ប៉ុន្តែ​វាប​ង្កើត​បានតែ ក្រាហ្វិក​ខ្សែ (line charts) ប៉ុណ្ណោះ។ គ្រប់​ជម្រើស​នៅក្នុង API របស់​វា​គឺ​ទាក់ទង​តែ​នឹង​អ័ក្ស x និង y (xAxisType, yAxisFormat, yAxisOrigin) គឺ​គ្មាន​ជម្រើស​ប្រភេទ​ក្រាហ្វិក (chart-type) ទាល់តែសោះ ដូច្នេះ​គ្មាន​ក្រាហ្វិក​ជួរឈរ (bar) ឬ​ក្រាហ្វិក​ចំណិត (pie) ឡើយ។ វា​ក៏បាន​បញ្ជាក់ react និង react-dom ជា direct dependencies បូករួម​ទាំង​កញ្ចប់ @visx/* ចំនួន​ប្រាំមួយ​ទៀត។ ហើយ​ការ​ចេញផ្សាយ​ចុងក្រោយ​របស់​វា​គឺ​នៅក្នុង​ខែវិច្ឆិកា ឆ្នាំ ២០២២។
  • @tufte/remark-chart — ជា remark plugin ពិតប្រាកដ ដូច្នេះ​វា​នឹង​ដំណើរការ​ជាមួយ Astro ហើយ​វា​មាន​ផ្តល់​ជម្រើស bar, line, scatter, histogram និង sparkline។ ប៉ុន្តែ គ្មាន​ក្រាហ្វិក​ចំណិត (pie) ឡើយ ហើយ renderer របស់​វាប​ង្កើត​លទ្ធផល​ជា ASCII ដែល​ត្រូវ​បាន​រចនា​ឡើង​សម្រាប់ terminal ច្រើនជាង​ទំព័រ web។
  • Vega-Lite — ល្អឥតខ្ចោះ​មែនទែន ប៉ុន្តែ​វា​មិន​ផ្អែកលើ​តារាង (table-driven) នោះ​ទេ។ អ្នក​ត្រូវ​សរសេរ​ការកំណត់ (specification) ដោយដៃ ដែល​វា​មាន​ភាព​ស៊ុក​រញ៉េរញ៉ៃ ផ្ទុយពី​អ្វី​ដែល​ខ្ញុំ​ព្យាយាម​ជៀសវាង។

សេចក្តី​សន្និដ្ឋាន​ដែល​មានប្រយោជន៍៖ អ្វី​ដែល​ខ្ញុំ​ចង់បាន​គឺ​មិនទាន់​មាន​ជា​កញ្ចប់​ទូទៅ (general-purpose package) ដែល​មានការ​ថែទាំ​នៅឡើយ​ទេ។ ការងារ​ដែល​នៅសល់​គឺ​មានត្រឹមតែ​ប្រហែល ២៥ ជួរ​នៃ​កូដ ប៉ុណ្ណោះ ដែល​តិច​ជាង​ដើមឈើ dependency (dependency tree) នៃ​ជម្រើស​ទាំងនោះ​ទៅទៀត។

របៀប​ដែល​វា​ដំណើរការ

មាន​ពីរ​ផ្នែក ដោយមាន​ការបែងចែក​ការងារ​យ៉ាង​ច្បាស់លាស់៖

Markdown source
│
├── <!--chart type="bar" height="280"--> ← a comment, invisible in Obsidian
│
├── | Quarter | Revenue |
│ | ------- | ------- |
│ | Q1 | 12,400 | ← a completely normal table
│
▼
remark plugin → <div data-chart="bar" data-chart-height="280"></div>
<div class="markdown-table-scroll"><table>…</table></div>
(the table is left exactly as it was)
▼
client script → reads the table, draws a canvas, then hides the table

តារាង​ទិន្នន័យ​មិន​ត្រូវ​បាន​កែប្រែ​ឡើយ។ នេះ​គឺជា​ការសម្រេច​ចិត្ត​ដ៏​សំខាន់​បំផុត​ក្នុង​រចនា​បទ​ទាំងមូល ហើយ​វា​គឺជា​អ្វី​ដែល​ធ្វើ​ឱ្យ​មុខងារ​នេះ​មាន​សុវត្ថិភាព៖

  • ទិន្នន័យ​នៅតែ​ជា​អត្ថបទ​ពិតប្រាកដ​នៅក្នុង​ទំព័រ ដូច្នេះ Pagefind អាច​ស្វែងរក​វា​បាន។
  • កម្មវិធី​អាន​អេក្រង់ (Screen readers) អាច​អាន​វា​បាន។
  • ប្រសិនបើ JavaScript បរាជ័យ CDN មិន​អាច​តភ្ជាប់​បាន ឬ​លេខ​មិន​អាច parse បាន អ្នកអាន​នៅតែ​មើលឃើញ​តារាង​ដដែល។

តារាង​ត្រូវ​បាន​លាក់ លុះត្រាតែ​ក្រាហ្វិក​ត្រូវ​បាន​គូរ​រួចរាល់​ពិតប្រាកដ។

ជំហាន​ទី ១៖ remark plugin

ការងារ​ទាំងមូល​របស់ plugin គឺ​បម្លែង comment ទៅជា​ធាតុ​ចំណាំ (marker element) ដែល​ស្ថិតនៅ​ចំពីមុខ​តារាង។

src/plugins/remark-table-chart.ts:

type MdastNode = {
type?: string;
value?: string;
data?: {
hName?: string;
hProperties?: Record<string, unknown>;
};
children?: MdastNode[];
};
// <!--chart type="bar" height="300" title="Revenue"-->
const DIRECTIVE_PATTERN = /^<!--\s*chart\b([^>]*?)\s*-->$/;
const CHART_TYPES = new Set(['bar', 'line', 'pie', 'stacked-bar']);
const MAX_HEIGHT = 720;
const MIN_HEIGHT = 120;
function parseAttributes(source: string) {
const attributes: Record<string, string> = {};
for (const match of source.matchAll(/([a-zA-Z][\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
attributes[match[1].toLowerCase()] = match[2] ?? match[3] ?? '';
}
return attributes;
}
function createMarker(attributes: Record<string, string>): MdastNode {
const type = (attributes.type ?? 'bar').toLowerCase();
const properties: Record<string, unknown> = {
dataChart: CHART_TYPES.has(type) ? type : 'bar',
};
const height = Number.parseInt(attributes.height ?? '', 10);
if (Number.isFinite(height)) {
properties.dataChartHeight = String(Math.min(Math.max(height, MIN_HEIGHT), MAX_HEIGHT));
}
if (attributes.title) {
properties.dataChartTitle = attributes.title;
}
if (attributes.unit) {
properties.dataChartUnit = attributes.unit;
}
return {
type: 'paragraph',
data: { hName: 'div', hProperties: properties },
children: [],
};
}
export default function remarkTableChart() {
return (tree: MdastNode) => {
const children = tree.children;
if (!children) return;
for (let index = 0; index < children.length; index += 1) {
const node = children[index];
if (node.type !== 'html' || typeof node.value !== 'string') continue;
const match = node.value.trim().match(DIRECTIVE_PATTERN);
if (!match) continue;
// Only convert when a table actually follows, so an unrelated comment
// that happens to start with "chart" is left alone.
if (children[index + 1]?.type !== 'table') continue;
children[index] = createMarker(parseAttributes(match[1] ?? ''));
}
};
}

មាន​លម្អិត​ពីរ​ចំណុច​នៅក្នុង​នោះ​ដែល​ជា​ចំណុច​គន្លឹះ៖

  • children[index + 1]?.type !== 'table' — comment ត្រូវ​បាន​បម្លែង​តែ​នៅពេល​ដែល​មាន​តារាង​ស្ថិតនៅ​បន្ទាប់បន្សំ​ពិតប្រាកដ។ បើ​គ្មាន​ចំណុច​នេះ​ទេ comment ផ្សេងទៀត​ដែល​ចាប់ផ្តើម​ដោយ​ពាក្យ “chart” នឹង​ក្លាយជា marker ដែល​ខូច​ដោ​យស្ងា​ត់ៗ។

  • CHART_TYPES.has(type) ? type : 'bar' — ប្រភេទ​ដែល​មិន​ស្គាល់​នឹង​ធ្លាក់​ទៅ​ប្រើ bar ជំនួស​ឱ្យ​ការ throw error ដូច្នេះ​ប្រសិនបើ​សរសេរខុស​អក្ខរា​វិរុទ្ធ វា​នឹង​ថយ​ចុះ​កម្រិត (degrade) ជាជាង​ធ្វើ​ឱ្យ build ខូច។ ការ​ចុះឈ្មោះ​នៅក្នុង astro.config.mjs៖

markdown: {
// Khmer is unspaced, so the built HTML gets invisible word breaks first.
remarkPlugins: [remarkKhmerBreaks, remarkTableChart, ...astroPluginConfig.remarkPlugins],
rehypePlugins: astroPluginConfig.rehypePlugins,
},

The output is a single empty element that carries the options:

<div data-chart="bar" data-chart-height="280" data-chart-title="Revenue by quarter"></div>

ជំហាន​ទី ២៖ ស្គ្រីប client

ការ Parse តារាង

ជួរឈរ​ដំបូង​គេ​ក្លាយជា​ស្លាក​ប្រភេទ (category labels); ជួរឈរ​បន្ទាប់បន្សំ​ទាំងអស់​ក្លាយជា series ដូច្នេះ​ក្រាហ្វិក​ដែល​មាន multi-series មិន​ចាំបាច់​មាន syntax បន្ថែម​ឡើយ។

// Khmer digits (០-៩) are not valid to Number(), so a table written with them
// would otherwise parse as a column of NaN and never render.
const KHMER_DIGITS = ['០', '១', '២', '៣', '៤', '៥', '៦', '៧', '៨', '៩'];
function toNumber(value: string): number | null {
const normalized = value.replace(/[០-៩]/g, (digit) => String(KHMER_DIGITS.indexOf(digit)));
const cleaned = normalized.replace(/[\s,]/g, '').replace(/%$/, '');
if (cleaned === '' || cleaned === '-' || cleaned === '—') return null;
const parsed = Number(cleaned);
return Number.isFinite(parsed) ? parsed : null;
}
function parseTable(table: HTMLTableElement): ChartTable | null {
const headers = Array.from(table.querySelectorAll('thead th')).map((cell) =>
(cell.textContent ?? '').trim(),
);
if (headers.length < 2) return null;
const rows = Array.from(table.querySelectorAll('tbody tr'));
const labels: string[] = [];
const columns: number[][] = Array.from({ length: headers.length - 1 }, () => []);
rows.forEach((row) => {
const cells = Array.from(row.querySelectorAll('td'));
if (cells.length < 2) return;
labels.push((cells[0].textContent ?? '').trim());
for (let column = 1; column < headers.length; column += 1) {
columns[column - 1].push(toNumber(cells[column]?.textContent ?? '') ?? Number.NaN);
}
});
if (labels.length === 0) return null;
// A column is only chartable when every value in it parsed as a number.
const series = columns
.map((values, index) => ({ name: headers[index + 1], data: values }))
.filter((entry) => !entry.data.some((value) => Number.isNaN(value)));
if (series.length === 0) return null;
return { labels, series };
}

filter គឺជា​បណ្តាញ​ការពារ​សុវត្ថិភាព។ ប្រសិនបើ​ប្រអប់ (cell) តែមួយ​នៅក្នុង​ជួរឈរ​មិនមែន​ជា​លេខ​ទេ Series ទាំងមូល​នោះ​នឹង​ត្រូវ​បោះបង់ចោល ជំនួស​ឱ្យ​ការ​យក​ទៅ​ប្លូ​ត​ជា​លេខ​សូន្យ​ដែល​នាំ​ឱ្យ​យល់​ច្រឡំ។ ប្រសិនបើ គ្រប់ series ទាំងអស់​ត្រូវ​បាន​បោះបង់ចោល parseTable នឹង​ត្រឡប់មកវិញ​នូវ null ហើយ​អ្នក​ហៅ​ប្រើប្រាស់​នឹង​ទុក​ឱ្យ​តារាង​នៅតែ​មើលឃើញ​ដដែល។

ការ​ដើរតាម theme របស់​គេហទំព័រ

ពណ៌​អត្ថបទ, grid និង tooltip ត្រូវ​បាន​អាន​ចេញពី CSS variables របស់​គេហទំព័រ​ផ្ទាល់ ដូច្នេះ​ក្រាហ្វិក​នឹង​ត្រូវគ្នា​ទៅតាម palette និង theme ដែល​កំពុង​សកម្ម។ ពណ៌​របស់ Series ត្រូវ​កំណត់​ជាក់លាក់ (hardcoded) ព្រោះ​ពណ៌ accent តែមួយ​មិន​អាចប​ង្កើត series ប្រាំមួយ​ដែល​មាន​លក្ខណៈ​ខុសគ្នា​ស្រឡះ​ដោយ​ភ្នែក​បាន​ឡើយ៖

function themeColors() {
const styles = getComputedStyle(document.documentElement);
const read = (name: string, fallback: string) => {
const value = styles.getPropertyValue(name).trim();
return value || fallback;
};
const isDark = document.documentElement.dataset.theme === 'dark';
return {
text: read('--paper-ink', isDark ? '#e7e7ea' : '#1c1e21'),
muted: read('--paper-ink-soft', isDark ? '#a6a6ad' : '#5c6066'),
grid: read('--paper-line', isDark ? '#33353a' : '#e2e2e6'),
surface: read('--paper-bg', isDark ? '#131417' : '#ffffff'),
};
}
const SERIES_COLORS = ['#4c8d6d', '#5b7fd4', '#c2703d', '#8d6bb5', '#3f9aa5', '#b8556f'];

សម្រាប់​ការ​ប្តូរ theme គេហទំព័រ​កំណត់ data-theme លើ <html> ដូច្នេះ MutationObserver គឺ​គ្រប់គ្រាន់​ហើយ៖

const observer = new MutationObserver(() => {
void paint(markers().filter((marker) => marker.dataset.chartPainted === 'true'));
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme'],
});

ការ​គូរ​ឡើងវិញ​ប្រើប្រាស់ canvas ដែល​មានស្រាប់​ឡើងវិញ (existing?.destroy()) ជំនួស​ឱ្យ​ការបង្កើត​ថ្មី ដូច្នេះ​ការ​ប្តូរ theme មិន​ធ្វើ​ឱ្យ​ទំព័រ​លោត​ផ្លោះ​ឡើយ។

Bugs ចំនួន​បី​ដែល​ខ្ញុំ​បាន​ជួប និង​មូលហេតុ​របស់​វា

នេះ​គឺជា​ផ្នែក​ដែល​ចំណាយពេល​របស់ខ្ញុំ​ពិតប្រាកដ ដូច្នេះ​វា​ជា​រឿង​ដែល​គួរតែ​កត់ត្រា​ទុក។

១. ក្រាហ្វិក​រីកធំឡើង​ដោយ​គ្មាន​ដែន​កំណត់

នេះ​មើលទៅដូចជា rendering bug ហើយ​វា​អាក្រក់​បំផុត​ក្នុងចំណោម​ទាំង​បី ព្រោះ​វា​ពិបាក​រកហេតុផល​បំផុត។ ខ្ញុំ​បាន​កំណត់​កម្ពស់​នៅ​លើ canvas៖

canvas.style.height = `${height}px`; // this is the bug

Chart.js ដែល​មាន responsive: true វាស់ container របស់​វា​ដើម្បី​សម្រេច​ទំហំ canvas។ ដោយសារតែ canvas គឺជា​អ្វី​ដែល​កំពុង​ត្រូវ​វាស់ និង​ជា​អ្វី​ដែល​កំពុង​កំណត់​កម្ពស់​ក្នុង​ពេល​តែមួយ នោះ​ការ​រត់​នី​មួយ​ៗ​បង្កើត​បាន​លទ្ធផល​ធំជាង​មុន​បន្តិចបន្តួច៖

Chart.js កំណត់​ទំហំ canvas ពី container របស់​វា → container រីក​ធំ​ដោយសារតែ canvas រីក​ធំ → Chart.js កំណត់​ទំហំ canvas ពី container ម្តង​ទៀត…

ដំណោះស្រាយ​គឺ​ត្រូវ​ដាក់​កម្ពស់​នៅ​លើ wrapper ហើយ​មិន​ត្រូវ​ដាក់​នៅ​លើ canvas ឡើយ៖

plot = document.createElement('div');
plot.className = 'table-chart__plot';
plot.style.height = `${plotHeight}px`;
const canvas = document.createElement('canvas');
plot.append(canvas);

CSS ក៏​សំខាន់​ដូចគ្នា​ដែរ។ ខ្ញុំ​ក៏​ត្រូវ​លុប width: 100% !important ចេញពី canvas ផង​ដែរ ព្រោះ​ការ​បង្ខំ​វិមាត្រ​ណាមួយ​នឹង​ជះឥទ្ធិពល​ប្រឆាំង​នឹង​ការកំណត់​ទំហំ​ផ្ទាល់​របស់ Chart.js៖

.table-chart__plot {
position: relative;
width: 100%;
}
.table-chart__plot canvas {
display: block;
}

ខ្ញុំ​មិន​ទុកចិត្ត​តែ​លទ្ធផល​មើលឃើញ​ដោយ​ភ្នែកទទេ។ ដូច្នេះ​ខ្ញុំ​បាន​ប្រើប្រាស់ browser ពិតប្រាកដ និង​ធ្វើការ​តេ​ស្ត​ទំហំ​ចំនួន ៦ ដង​ក្នុងរយៈពេល ៤ វិនាទី៖

t=0: 280px plot=280 canvas=1104x280 | 260px plot=260 canvas=1104x260
t=1: 280px plot=280 canvas=1104x280 | 260px plot=260 canvas=1104x260
t=2: 280px plot=280 canvas=1104x280 | 260px plot=260 canvas=1104x260
t=3: 280px plot=280 canvas=1104x280 | 260px plot=260 canvas=1104x260
t=4: 280px plot=280 canvas=1104x280 | 260px plot=260 canvas=1104x260
t=5: 280px plot=280 canvas=1104x280 | 260px plot=260 canvas=1104x260
លទ្ធផល: STABLE

២. តារាង​មិន​ស្ថិតនៅ​កន្លែង​ដែល​ខ្ញុំ​រំពឹង​ទុក

គ្មាន​ក្រាហ្វិក​ណាមួយ​បង្ហាញ​ទាល់តែសោះ។ មូលហេតុ៖ plugin responsiveTables ដែល​មក​ជាមួយ theme នេះ ខ្ចប់​គ្រប់​តារាង​ទាំងអស់​នៅក្នុង <div class="markdown-table-scroll"> ដូច្នេះ nextElementSibling គឺជា div ហើយ​មិនដែល​ជា HTMLTableElement ឡើយ។

// Would never match
const table = marker.nextElementSibling;
// The table is the next sibling, or the first table inside it
const sibling = marker.nextElementSibling;
const table =
sibling instanceof HTMLTableElement ? sibling : sibling?.querySelector('table');

វា​ជា​រឿង​ល្អ​ដែល​ត្រូវ​ពិនិត្យមើល output នៃ theme របស់​ប្រព័ន្ធ​អ្នក មុន​ពេល​សន្មត​ថា​ទំនាក់ទំនង sibling ត្រូវ​បាន​រក្សា។

៣. លេខ​ខ្មែរ​មិន​អាច​អាន​បាន ហើយ​គ្មាន​ចេញError

ចំណុច​នេះ​លេចឡើង​តែ​ដោយសារតែ​ខ្ញុំ​បាន​សរសេរ​អត្ថបទ​ជា​ភាសាខ្មែរ និង​បាន​ប្រើប្រាស់​លេខ​ខ្មែរ​ពិតប្រាកដ។

Number('១២០') // NaN

ក្រាហ្វិក​មិន​បង្ហាញ error មិន​ផ្តល់​ការព្រមាន ហើយ​គ្រាន់តែ​មិន​បង្ហាញ​ចេញ​មក​តែម្តង។ ឥឡូវនេះ toNumber ធ្វើ​ឱ្យ​លេខ​ខ្មែរ​ក្លាយជា ASCII ជាមុន​សិន ដែល​ជាជួរ​នៅ​ផ្នែក​ខាងលើ​នៃ parser ខាងលើ។ ប្រសិនបើ​គេហទំព័រ​របស់​អ្នក​មិនមែន​ជា​ភាសាខ្មែរ​ទេ អ្នក​អាច​លុប​ជួរ​នោះ​ចេញ​បាន — ប៉ុន្តែ​វា​គឺជា​ឧទាហរណ៍​ដ៏​ល្អ​នៃ​បរាជ័យ​ដែល​កើតឡើង​ដោយ​ស្ងៀមស្ងាត់​ទាំងស្រុង។

ការប្រៀបធៀប និង​របៀប​ប្រើប្រាស់

បន្ថែម comment មួយ​ជួរ​នៅ​ពីមុខ​តារាង​ណាមួយ៖

<!--chart type="bar" title="Revenue by quarter" height="280"-->
| Quarter | Revenue | Costs |
| ------- | ------- | ----- |
| Q1 | 12,400 | 8,100 |
| Q2 | 15,900 | 9,300 |
AttributeValuesDefaultPurpose
typebar, line, stacked-bar, piebarប្រភេទ​ក្រាប
titleអក្សរ​អ្វី​ក៏បាន—ចំណងជើង​នៅ​លើ​ក្រាប
height120–720300កម្ពស់​របស់ Plot ជា pixels
unitអក្សរ​អ្វី​ក៏បាន—អក្សរ​សម្រាប់ tooltip

លេខ​អាច​ត្រូវ​បាន​សរសេរ​ដោយមាន​សញ្ញា​ខណ្ឌ​ពាន់ សញ្ញា % នៅ​ខាងចុង ឬ​ជា​លេខ​ខ្មែរ។

ឧទាហរណ៍​ផ្ទាល់ ដែល​ត្រូវ​បាន render នៅ​លើ​ទំព័រ​នេះ៖

DaySignups
Mon120
Tue180
Wed150
Thu240
Fri310

ហើយ​នេះ​គឺជា​ករណី​បរាជ័យ​ដោយចេតនា។ គ្រប់​តម្លៃ​ខាងក្រោម​គឺជា​អត្ថបទ ដូច្នេះ​គ្មាន series ណា​អាចប​ង្កើត​បាន​ឡើយ ដូច្នេះ​តារាង​នៅតែ​ស្ថិតនៅ​កន្លែង​ដើម​ដដែល៖

NameNote
Adalots
Gracesome

ផល​ប៉ះពាល់

មិន​មាន​អ្វី​ត្រូវ​ដំឡើង​ឡើយ។ គ្មាន React, គ្មាន framework, គ្មាន​ជំហាន build។ Chart.js ត្រូវ​បាន​ទាញ​យក​ពី CDN តែ​នៅពេល​ដែល​ទំព័រ​មាន​ក្រាហ្វិក​ពិតប្រាកដ​ប៉ុណ្ណោះ ហើយ​វា​ត្រូវ​បាន​រក្សាទុក (cached) ដោយ browser បន្ទាប់ពី​នោះ — ទំព័រ​ដែល​គ្មាន​ក្រាហ្វិក នឹង​មិន​ទាញ​យក​វា​ឡើយ។

ការអនុវត្ត​ទាំងមូល​គឺ​មានត្រឹមតែ remark plugin មួយ និង​ស្គ្រីប​មួយ​ប៉ុណ្ណោះ។ សម្រាប់​ការប្រៀបធៀប ការ​បន្ថែម Recharts ទៅកាន់​គេហទំព័រ​នេះ មានន័យថា​ត្រូវ​ដំឡើង @astrojs/react, react, react-dom និង recharts ដែល​សរុប​មក​មាន​ទំហំ​ប្រហែល ៥ ដង​នៃ​ទំហំ JavaScript សរុប​បច្ចុប្បន្ន​របស់​គេហទំព័រ នៅ​លើ​ប្លុក​ដែល​បច្ចុប្បន្ន​ផ្ញើ​ចេញ​ប្រហែល 39 KB នៃ JS ដែល​គ្មាន framework ក្នុង​មួយ​អត្ថបទ​ទេ។

ជា​សង្ខេប

  • តារាង Markdown បូករួម​ទាំង comment មួយ​ជួរ ក្លាយជា​ក្រាហ្វិក។ គ្មាន syntax បន្ថែម​សម្រាប់​ទិន្នន័យ multi-series ឡើយ។
  • តារាង​គឺជា​ប្រភព​នៃ​ការពិត (source of truth) ហើយ​មិនដែល​ត្រូវ​បាន​លុបចោល​ឡើយ លុះត្រាតែ​ក្រាហ្វិក​ត្រូវ​បាន render យ៉ាង​ពិតប្រាកដ។
  • គ្មាន dependencies ថ្មី គ្មាន framework គ្មាន​ការកំណត់​រចនាសម្ព័ន្ធ build។
  • ចំណាយ​ត្រឹមតែ​កូដ parse ប្រហែល ២៥ ជួរ បូករួម​ទាំង​ពេលវេលា debugging សម្រាប់ bug ទាំង​បី​ខាងលើ។

ប្រសិនបើ​អ្នក​កំពុង​បង្កើត​គេហទំព័រ​ដែល​ក្រាហ្វិក​គ្រាន់តែ​ជាផ្នែក​រង នេះ​គឺជា​ជម្រើស​ដ៏​ល្អ។ ប៉ុន្តែ​ប្រសិនបើ​ក្រាហ្វិក​គឺជា គោលបំណង​ចម្បង នៃ​គេហទំព័រ component សមស្រប​មួយ​ដែល​មាន Vega-Lite ឬ Recharts នឹង​មាន​សមត្ថភាព​ពង្រីក (scale) បាន​ល្អ​ជាង — ប៉ុន្តែ​នោះ​គឺជា​គម្រោង​ផ្សេង​ពីនេះ។