@dschz/ 固态升起
固态JS包装器 uPlot 地图 ——用于时间序列数据的超快小脚印图库.
* 特性
- uPlot周围的全反应固体JS包装器
- 插件系统支持与插件间通信
- 对图表生命周期的精细控制
- 轻而易举
- 框外的 QQ 类型脚本支持
- QQ 用于工具提示、 传说、 光标跟踪和系列焦点的内置插件
- * 以自动调整规模的能力响应规模支持
* 安装
npm install solid-js uplot @dschz/solid-uplot
pnpm install solid-js uplot @dschz/solid-uplot
yarn install solid-js uplot @dschz/solid-uplot
bun install solid-js uplot @dschz/solid-uplot
现场直播
这艘反转船有一个 直播,操场 ——全面展示图书馆每个特色. 这是最快的方法 solid-uplot 并探索图表、插件、响应大小和外部集成等真实的、可行的实例.
运行游戏场
git clone https://github.com/dsnchz/solid-uplot.git
cd solid-uplot
# Install dependencies
bun install
# Start the playground
bun start
游乐场运行在 http://localhost:3000 并包括交互式演示:
- 所有内置插件( 曲线、 工具提示、 传说、 焦点系列)
- 响应和自动调整图型
- 自定义插件开发
- 通过插件总线进行外部组件集成
QQ 软件包结构
这个软件包为不同的功能提供了三个主要导出路径:
@dschz/solid-uplot
核心组件和插件系统 :
import { SolidUplot, createPluginBus } from "@dschz/solid-uplot";
import type {
SolidUplotPluginBus,
UplotPluginFactory,
UplotPluginFactoryContext,
} from "@dschz/solid-uplot";
@dschz/solid-uplot/plugins
此导出路径提供了四个插件(其中三个可以被认为是原始的).
cursor: 传送光标位置数据focusSeries: 传送在视觉上强调的系列tooltip: 允许您在光标周围显示自定义 JSX 工具提示的插件legend: 插件, 允许您在画布绘制区域上将自定义 JSX 组件作为您的传说 .
import { cursor, tooltip, legend, focusSeries } from "@dschz/solid-uplot/plugins";
import type {
CursorPluginMessageBus,
FocusSeriesPluginMessageBus,
TooltipProps,
LegendProps,
} from "@dschz/solid-uplot/plugins";
@dschz/solid-uplot/utils
从 a 获取某些位点数据的便利工具函数 uPlot 实例(除 getColorString 翻译系列 stroke 或者说 fill 输入颜色值).
import {
getSeriesData,
getCursorData,
getColorString,
getNewCalendarDayIndices,
} from "@dschz/solid-uplot/utils";
import type { SeriesDatum, CursorData } from "@dschz/solid-uplot/utils";
QQ 快速启动
import { SolidUplot, createPluginBus } from "@dschz/solid-uplot";
import { cursor, tooltip, legend } from "@dschz/solid-uplot/plugins";
import type { CursorPluginMessageBus, TooltipProps, LegendProps } from "@dschz/solid-uplot/plugins";
// Create a tooltip component
const MyTooltip = (props: TooltipProps) => (
<div style={{ background: "white", padding: "8px", border: "1px solid #ccc" }}>
<div>X: {props.cursor.xValue}</div>
<For each={props.seriesData}>
{(series) => {
const value = props.u.data[series.seriesIdx]?.[props.cursor.idx];
return (
<div>
{series.label}: {value}
</div>
);
}}
</For>
</div>
);
// Create a legend component
const MyLegend = (props: LegendProps) => (
<div style={{ background: "rgba(255,255,255,0.9)", padding: "8px" }}>
<For each={props.seriesData}>
{(series) => (
<div style={{ display: "flex", "align-items": "center", gap: "4px" }}>
<div
style={{
width: "12px",
height: "12px",
background: series.stroke,
}}
/>
<span>{series.label}</span>
</div>
)}
</For>
</div>
);
const MyChart = () => {
const bus = createPluginBus<CursorPluginMessageBus>();
return (
<SolidUplot
data={[
[0, 1, 2, 3], // x values
[10, 20, 30, 40], // y values for series 1
[15, 25, 35, 45], // y values for series 2
]}
width={600}
height={400}
series={[
{},
{ label: "Series 1", stroke: "#1f77b4" },
{ label: "Series 2", stroke: "#ff7f0e" },
]}
plugins={[
cursor(),
tooltip(MyTooltip),
legend(MyLegend, { placement: "top-right", pxOffset: 12 }),
]}
pluginBus={bus}
/>
);
};
响应大小
对于自动适应容器大小变化的响应性图表,使用 autoResize 道具 :
<div style={{ width: "100%", height: "400px" }}>
<SolidUplot
autoResize={true}
data={data}
series={series}
// Chart will automatically resize to fill the container
/>
</div>
对于更先进的响应模式,您可以将这个库与 @dschz/ 固态自动喷雾器数字 :
npm install @dschz/solid-auto-sizer
pnpm install @dschz/solid-auto-sizer
yarn install @dschz/solid-auto-sizer
bun install @dschz/solid-auto-sizer
import { AutoSizer } from "@dschz/solid-auto-sizer";
<AutoSizer>
{({ width, height }) => <SolidUplot width={width} height={height} data={data} />}
</AutoSizer>;
或者,你可以使用 createElementSize 从 @solid-primitives/resize-observer 基于信号的方法:
npm install @solid-primitives/resize-observer
import { createElementSize } from "@solid-primitives/resize-observer";
const ResponsiveChart = () => {
let container!: HTMLDivElement;
const size = createElementSize(() => container);
return (
<div style={{ width: "100%", height: "400px" }}>
<div ref={container} style={{ width: "100%", height: "100%" }}>
<SolidUplot data={data} width={size.width ?? 0} height={size.height ?? 0} series={series} />
</div>
</div>
);
};
增强插件系统
其基本特征是: SolidUplot 是其精细的插件系统,通过反应性消息总线,能够扩展功能并进行插件间通信.
插件总线架构
插件总线系统使插件能够通过反应存储器相互和外部组件通信. 这一架构提供:
- 安全型通信: 所有插件信件已全部输入
- 反应更新: 插件状态的变化自动触发更新
- 脱钩组件: 插件可以在没有直接依赖的情况下相互作用
- 广泛: 方便添加与现有插件相融合的新插件
内置插件
光标插件
跟踪光标位置和跨图表的交互状态:
import { cursor } from "@dschz/solid-uplot/plugins";
import type { CursorPluginMessageBus } from "@dschz/solid-uplot/plugins";
const cursorPlugin = cursor();
光标插件提供光标位置数据,其他插件可以通过总线消耗.
焦点系列插件
基于光标相近性的突出显示系列 :
import { focusSeries } from "@dschz/solid-uplot/plugins";
import type { FocusSeriesPluginMessageBus } from "@dschz/solid-uplot/plugins";
const focusPlugin = focusSeries({
pxThreshold: 15, // Distance threshold for focusing (default: 15)
});
工具提示插件
装有自动定位和外溢处理的自定义工具提示:
import { tooltip } from "@dschz/solid-uplot/plugins";
import type { TooltipProps } from "@dschz/solid-uplot/plugins";
const MyTooltip: Component<TooltipProps> = (props) => {
return (
<div
style={{
background: "white",
border: "1px solid #ccc",
padding: "8px",
"border-radius": "4px",
"box-shadow": "0 2px 4px rgba(0,0,0,0.1)",
}}
>
<div style={{ "font-weight": "bold", "margin-bottom": "8px" }}>X: {props.cursor.xValue}</div>
<For each={props.seriesData}>
{(series) => {
const value = () => props.u.data[series.seriesIdx]?.[props.cursor.idx];
return (
<Show when={series.visible}>
<div style={{ display: "flex", "align-items": "center", "margin-bottom": "4px" }}>
<div
style={{
width: "10px",
height: "10px",
"border-radius": "50%",
"background-color": series.stroke,
"margin-right": "6px",
}}
/>
<span style={{ color: series.stroke }}>
{series.label}: {value()?.toFixed(2)}
</span>
</div>
</Show>
);
}}
</For>
</div>
);
};
const tooltipPlugin = tooltip(MyTooltip, {
placement: "top-left", // "top-left" | "top-right" | "bottom-left" | "bottom-right"
zIndex: 20,
});
图例插件
添加具有智能定位和交互特性的自定义的传说 :
import { legend } from "@dschz/solid-uplot/plugins";
import type { LegendProps } from "@dschz/solid-uplot/plugins";
const MyLegend: Component<LegendProps> = (props) => {
// Access cursor data for interactive features
const cursorVisible = () => props.bus.data.cursor?.state[props.u.root.id]?.visible;
return (
<div
style={{
background: "white",
border: "1px solid #ddd",
"border-radius": "4px",
padding: "8px",
"box-shadow": "0 2px 4px rgba(0,0,0,0.1)",
// Dim when tooltip is active
opacity: cursorVisible() ? 0.6 : 1,
transition: "opacity 200ms",
}}
>
<div style={{ "font-weight": "bold", "margin-bottom": "8px" }}>Legend</div>
<For each={props.seriesData}>
{(series) => (
<Show when={series.visible}>
<div
style={{
display: "flex",
"align-items": "center",
gap: "6px",
"margin-bottom": "4px",
}}
>
<div
style={{
width: "12px",
height: "12px",
"background-color": series.stroke,
"border-radius": "2px",
}}
/>
<span style={{ "font-size": "14px" }}>{series.label}</span>
</div>
</Show>
)}
</For>
</div>
);
};
const legendPlugin = legend(MyLegend, {
placement: "top-left", // "top-left" | "top-right"
pxOffset: 8, // Distance from chart edges (default: 8)
zIndex: 10,
});
图例插件特征 :
- 简单定位: 只有左上或右上角避免轴冲突
- 大小限制: 图例不能超过图表绘图区域维度
- 布局不可知: 控制内部布局和样式
- 非干涉: 设计与图表互动和谐工作
- 插件总线集成: 获取光标和焦点数据进行智能互动
- 自动清理: 适当的内存管理和DOM清理
插件总线类型安全
在使用多个插件时,通过正确打入插件总线确保类型安全:
import { createPluginBus } from "@dschz/solid-uplot";
import type {
CursorPluginMessageBus,
FocusSeriesPluginMessageBus,
} from "@dschz/solid-uplot/plugins";
// Create a bus that includes all plugin message types
const bus = createPluginBus<CursorPluginMessageBus & FocusSeriesPluginMessageBus>();
const MyChart = () => {
return (
<SolidUplot
data={data}
pluginBus={bus}
plugins={[cursor(), focusSeries(), tooltip(MyTooltip), legend(MyLegend)]}
/>
);
};
创建自定义插件
插件系统可扩展。 编写用于公共消费的插件时,遵循此模式:
import type { UplotPluginFactory } from "@dschz/solid-uplot";
import type { CursorPluginMessageBus } from "@dschz/solid-uplot/plugins";
// 1. Define your plugin's message type
export type MyPluginMessage = {
value: number;
timestamp: number;
};
// 2. Define your plugin's message bus
export type MyPluginMessageBus = {
myPlugin?: MyPluginMessage;
};
// 3. Export your plugin factory
export const myPlugin = (
options = {},
): UplotPluginFactory<CursorPluginMessageBus & MyPluginMessageBus> => {
return ({ bus }) => {
if (!bus) {
console.warn("[my-plugin]: A plugin bus is required");
return { hooks: {} };
}
return {
hooks: {
ready: (u) => {
// Initialize plugin state
bus.setData("myPlugin", {
value: 0,
timestamp: Date.now(),
});
},
setData: (u) => {
// Update plugin state
bus.setData("myPlugin", "value", (prev) => prev + 1);
},
},
};
};
};
外部构成部分
插件总线可以在图表和外部组件之间实现强大的集成:
import { createPluginBus } from "@dschz/solid-uplot";
import type { FocusSeriesPluginMessageBus } from "@dschz/solid-uplot/plugins";
const bus = createPluginBus<FocusSeriesPluginMessageBus>();
// External component that can trigger series focus
const DataGrid = (props: { bus: typeof bus }) => {
const handleRowHover = (seriesLabel: string) => {
props.bus.setData("focusSeries", {
sourceId: "data-grid",
targets: [{ label: seriesLabel }],
});
};
return <table>{/* Grid implementation */}</table>;
};
// Chart and grid interact through shared bus
const MyDashboard = () => {
return (
<div>
<DataGrid bus={bus} />
<SolidUplot data={data} pluginBus={bus} plugins={[focusSeries()]} />
</div>
);
};
API 参考
SolidUplot 组件
type SolidUplotEvents = {
/** Callback fired when the uPlot instance is created */
readonly onCreate?: (u: uPlot, meta: OnCreateMeta) => void;
/** Callback fired when the cursor moves */
readonly onCursorMove?: (params: OnCursorMoveParams) => void;
};
// Main component props (extends all uPlot.Options except plugins, width, height)
type SolidUplotProps<T extends VoidStruct = VoidStruct> = SolidUplotOptions<T> &
SolidUplotEvents & {
// Ref callback to access the chart container element
ref?: Ref<HTMLDivElement>;
// CSS class name for the chart container (default: "solid-uplot")
// Additional classes will be appended to the default class
class?: string;
// CSS styles for the chart container (position is managed internally)
style?: Omit<JSX.CSSProperties, "position">;
// Enable automatic resizing to fit container (default: false)
autoResize?: boolean;
// Whether to reset scales when chart data is updated (default: true)
resetScales?: boolean;
// Where to place children components relative to the chart (default: "top")
childrenPlacement?: "top" | "bottom";
};
// Configuration options extending uPlot.Options with SolidJS enhancements
type SolidUplotOptions<T extends VoidStruct = VoidStruct> = Omit<
uPlot.Options,
"plugins" | "width" | "height"
> & {
// Chart dimensions
width?: number; // default: 600
height?: number; // default: 300
// Plugin configuration
plugins?: SolidUplotPlugin<T>[];
pluginBus?: SolidUplotPluginBus<T>;
};
// Plugin type (can be standard uPlot plugin or factory function)
type SolidUplotPlugin<T extends VoidStruct = VoidStruct> = uPlot.Plugin | UplotPluginFactory<T>;
插件总线
// Plugin bus type (derived from createPluginBus return type)
type SolidUplotPluginBus<T extends VoidStruct = VoidStruct> = ReturnType<typeof createPluginBus<T>>;
// Create a plugin bus
const createPluginBus: <T extends VoidStruct = VoidStruct>(
initialData?: T,
) => SolidUplotPluginBus<T>;
内建插件选项
// Cursor Plugin
const cursor = (): UplotPluginFactory<CursorPluginMessageBus>;
// Focus Series Plugin
const focusSeries = (options?: {
pxThreshold?: number; // default: 15
}): UplotPluginFactory<CursorPluginMessageBus & FocusSeriesPluginMessageBus>;
// Tooltip Plugin
const tooltip = (
Component: Component<TooltipProps>,
options?: {
placement?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
id?: string;
class?: string;
style?: JSX.CSSProperties;
zIndex?: number; // default: 20
}
): UplotPluginFactory<CursorPluginMessageBus & FocusSeriesPluginMessageBus>;
// Legend Plugin
const legend = (
Component: Component<LegendProps>,
options?: {
placement?: "top-left" | "top-right"; // default: "top-left"
pxOffset?: number; // default: 8
id?: string;
class?: string;
style?: JSX.CSSProperties;
zIndex?: number; // default: 10
}
): UplotPluginFactory<CursorPluginMessageBus & FocusSeriesPluginMessageBus>;
实例
基本图表
import { SolidUplot } from "@dschz/solid-uplot";
const BasicChart = () => {
return (
<SolidUplot
data={[
[0, 1, 2, 3],
[10, 20, 30, 40],
[15, 25, 35, 45],
]}
width={600}
height={400}
scales={{
x: { time: false },
}}
series={[
{},
{ label: "Series 1", stroke: "#1f77b4" },
{ label: "Series 2", stroke: "#ff7f0e" },
]}
/>
);
};
带有全部插件的图表
import { SolidUplot, createPluginBus } from "@dschz/solid-uplot";
import { cursor, tooltip, legend, focusSeries } from "@dschz/solid-uplot/plugins";
import type {
CursorPluginMessageBus,
FocusSeriesPluginMessageBus,
TooltipProps,
LegendProps,
} from "@dschz/solid-uplot/plugins";
const MyTooltip: Component<TooltipProps> = (props) => (
<div style={{ background: "white", padding: "8px", border: "1px solid #ccc" }}>
<div>Time: {new Date(props.cursor.xValue * 1000).toLocaleTimeString()}</div>
<For each={props.seriesData}>
{(series) => {
const value = props.u.data[series.seriesIdx]?.[props.cursor.idx];
return (
<div style={{ color: series.stroke }}>
{series.label}: {value?.toFixed(2)}
</div>
);
}}
</For>
</div>
);
const MyLegend: Component<LegendProps> = (props) => {
const cursorVisible = () => props.bus.data.cursor?.state[props.u.root.id]?.visible;
return (
<div
style={{
background: "white",
border: "1px solid #ddd",
padding: "8px",
opacity: cursorVisible() ? 0.6 : 1,
transition: "opacity 200ms",
}}
>
<For each={props.seriesData}>
{(series) => (
<div style={{ display: "flex", "align-items": "center", gap: "6px" }}>
<div
style={{
width: "12px",
height: "12px",
background: series.stroke,
}}
/>
<span>{series.label}</span>
</div>
)}
</For>
</div>
);
};
const FullFeaturedChart = () => {
const bus = createPluginBus<CursorPluginMessageBus & FocusSeriesPluginMessageBus>();
return (
<SolidUplot
data={[
[0, 1, 2, 3, 4, 5],
[10, 20, 30, 40, 50, 60],
[15, 25, 35, 45, 55, 65],
[5, 15, 25, 35, 45, 55],
]}
width={800}
height={500}
series={[
{},
{ label: "Revenue", stroke: "#1f77b4" },
{ label: "Profit", stroke: "#ff7f0e" },
{ label: "Expenses", stroke: "#2ca02c" },
]}
plugins={[
cursor(),
focusSeries({ pxThreshold: 20 }),
tooltip(MyTooltip, { placement: "top-right" }),
legend(MyLegend, { placement: "top-left", pxOffset: 12 }),
]}
pluginBus={bus}
/>
);
};
响应图表
const ResponsiveChart = () => {
return (
<div style={{ width: "100%", height: "400px", border: "1px solid #ccc" }}>
<SolidUplot
autoResize={true}
data={data}
series={series}
plugins={[cursor(), tooltip(MyTooltip)]}
/>
</div>
);
};
外部一体化
const Dashboard = () => {
const bus = createPluginBus<FocusSeriesPluginMessageBus>();
const handleSeriesToggle = (seriesLabel: string) => {
bus.setData("focusSeries", {
sourceId: "external-control",
targets: [{ label: seriesLabel }],
});
};
return (
<div>
<div>
<button onClick={() => handleSeriesToggle("Series 1")}>Focus Series 1</button>
<button onClick={() => handleSeriesToggle("Series 2")}>Focus Series 2</button>
</div>
<SolidUplot data={data} plugins={[focusSeries()]} pluginBus={bus} />
</div>
);
};
捐款
欢迎捐款! 请随意提交拉力请求.
还有看 解析器 社区.
许可证
麻省理工学院