lightview
Version:
A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation
238 lines (200 loc) • 7.65 kB
JavaScript
/**
* Lightview Chart Component
* Powered by charts.css
* @see https://chartscss.org/
*/
import '../daisyui.js';
/**
* Chart Component
* @param {Object} props
* @param {string} props.type - 'bar' | 'column' | 'line' | 'area' | 'pie'
* @param {string} props.heading - Table caption / Chart heading
* @param {boolean} props.labels - Show labels (.show-labels)
* @param {boolean} props.dataOnHover - Show data on hover (.show-data-on-hover)
* @param {boolean} props.primaryAxis - Show primary axis (.show-primary-axis)
* @param {boolean|string} props.secondaryAxis - Show secondary axes (.show-10-secondary-axes or custom class)
* @param {number} props.spacing - Data spacing (1-20) (.data-spacing-X)
* @param {boolean} props.reverse - Reverse orientation (.reverse)
* @param {boolean} props.multiple - Enable multiple datasets (.multiple)
* @param {boolean} props.stacked - Enable stacked mode (.stacked)
* @param {boolean} props.useShadow - Render in Shadow DOM with isolated DaisyUI styles
* @param {string} props.class - Additional classes
*/
const CHARTS_CSS_URL = 'https://cdn.jsdelivr.net/npm/charts.css/dist/charts.min.css';
// Register stylesheet for Shadow DOM usage (Adopted StyleSheets)
// Using top-level await in module to ensure it's loaded before any component renders
if (globalThis.LightviewX?.registerStyleSheet) {
await LightviewX.registerStyleSheet(CHARTS_CSS_URL);
}
// Auto-load charts.css for Global/Light DOM usage
if (typeof document !== 'undefined') {
if (!document.querySelector(`link[href^="${CHARTS_CSS_URL}"]`)) {
// Preload for better performance
const preload = document.createElement('link');
preload.rel = 'preload';
preload.as = 'style';
preload.href = CHARTS_CSS_URL;
document.head.appendChild(preload);
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = CHARTS_CSS_URL;
document.head.appendChild(link);
}
}
const Chart = (props = {}, ...children) => {
const { tags } = globalThis.Lightview || {};
const LVX = globalThis.LightviewX || {};
if (!tags) return null;
const { table, caption, div, shadowDOM } = tags;
const {
type = 'bar',
heading,
labels = false,
dataOnHover = false,
primaryAxis = false,
secondaryAxesCount,
secondaryAxis = false,
reverse = false,
multiple = false,
stacked = false,
useShadow,
class: className = '',
style = '',
...rest
} = props;
const classes = ['charts-css', type];
if (labels) classes.push('show-labels');
if (dataOnHover) classes.push('show-data-on-hover');
if (heading) classes.push('show-heading');
if (primaryAxis) classes.push('show-primary-axis');
if (secondaryAxesCount) {
classes.push(`show-${secondaryAxesCount}-secondary-axes`);
} else if (secondaryAxis === true) {
classes.push('show-10-secondary-axes');
} else if (typeof secondaryAxis === 'string') {
classes.push(secondaryAxis);
}
if (reverse) classes.push('reverse');
if (multiple) classes.push('multiple');
if (stacked) classes.push('stacked');
if (className) classes.push(className);
const content = [];
if (heading) {
content.push(caption(heading));
}
content.push(...children);
const chartEl = table({ class: classes.join(' '), style, ...rest }, ...content);
// Check if we should use shadow DOM
let usesShadow = false;
if (LVX.shouldUseShadow) {
usesShadow = LVX.shouldUseShadow(useShadow);
} else {
usesShadow = useShadow === true;
}
if (usesShadow) {
const rawSheets = LVX.getAdoptedStyleSheets ? LVX.getAdoptedStyleSheets(null, [CHARTS_CSS_URL]) : [];
// Separate CSSStyleSheet objects from URL string fallbacks
const adoptedStyleSheets = rawSheets.filter(s => typeof s !== 'string');
const styles = rawSheets.filter(s => typeof s === 'string');
const themeValue = LVX.themeSignal ? () => LVX.themeSignal.value : 'light';
return div({ class: 'contents' },
shadowDOM({ mode: 'open', adoptedStyleSheets, styles },
div({ 'data-theme': themeValue },
chartEl
)
)
);
}
return chartEl;
};
/**
* Chart Head (thead)
*/
Chart.Head = (props = {}, ...children) => {
const { tags } = globalThis.Lightview || {};
if (!tags) return null;
return tags.thead(props, ...children);
};
/**
* Chart Body (tbody)
*/
Chart.Body = (props = {}, ...children) => {
const { tags } = globalThis.Lightview || {};
if (!tags) return null;
return tags.tbody(props, ...children);
};
/**
* Chart Row (tr)
*/
Chart.Row = (props = {}, ...children) => {
const { tags } = globalThis.Lightview || {};
if (!tags) return null;
return tags.tr(props, ...children);
};
/**
* Chart Label (th scope="row")
*/
Chart.Label = (props = {}, ...children) => {
const { tags } = globalThis.Lightview || {};
if (!tags) return null;
const { scope = 'row', ...rest } = props;
return tags.th({ scope, ...rest }, ...children);
};
/**
* Chart Data (td)
* @param {Object} props
* @param {number} props.value - Value (0.0 - 1.0) -> --size (for bar/column/line/area charts)
* @param {number} props.start - Start value (0.0 - 1.0) -> --start (for range/waterfall and pie)
* @param {number} props.end - End value (0.0 - 1.0) -> --end (for pie charts - use instead of value)
* @param {string} props.color - CSS color -> --color
* @param {string} props.tooltip - Tooltip text
*/
Chart.Data = (props = {}, ...children) => {
const { tags } = globalThis.Lightview || {};
if (!tags) return null;
const {
value,
size, // alias for value
start,
end, // for pie charts
color,
tooltip,
class: className = '',
style: styleProp = '',
...rest
} = props;
const styles = [];
const val = value !== undefined ? value : size;
// For bar/column/line/area charts: use --size
if (val !== undefined) styles.push(`--size: ${val};`);
if (start !== undefined) styles.push(`--start: ${start};`);
// For Pie charts: use --end directly if provided, otherwise calculate from start + value
if (end !== undefined) {
styles.push(`--end: ${end};`);
} else if (start !== undefined && val !== undefined) {
// Legacy: Calculate --end for backward compatibility
const calculatedEnd = parseFloat(start) + parseFloat(val);
styles.push(`--end: ${calculatedEnd};`);
}
if (color) styles.push(`--color: ${color};`);
if (styleProp) styles.push(styleProp);
const classes = [];
if (className) classes.push(className);
const content = [...children];
if (tooltip) {
content.push(tags.span({ class: 'tooltip' }, tooltip));
}
return tags.td({
class: classes.join(' ') || undefined,
style: styles.join(' ') || undefined,
...rest
}, ...content);
};
const tags = globalThis.Lightview.tags;
tags.Chart = Chart;
tags['Chart.Head'] = Chart.Head;
tags['Chart.Body'] = Chart.Body;
tags['Chart.Row'] = Chart.Row;
tags['Chart.Label'] = Chart.Label;
tags['Chart.Data'] = Chart.Data;
export default Chart;