搜索
首页web前端css教程制作图表?尝试使用MOBX状态树为数据供电

Making a Chart? Try Using Mobx State Tree to Power the Data

谁喜欢图表?每个人都喜欢,对吧?创建图表的方法有很多,包括许多库。例如 D3.js、Chart.js、amCharts、Highcharts 和 Chartist,这只是众多选项中的一小部分。

但是我们并不一定需要图表库来创建图表。Mobx-state-tree (MST) 是 Redux 的一种直观替代方案,用于管理 React 中的状态。我们可以使用简单的 SVG 元素构建交互式自定义图表,并使用 MST 来管理和操作图表的 数据。如果您过去曾尝试使用 D3.js 等工具构建图表,我认为您会发现这种方法更直观。即使您是经验丰富的 D3.js 开发人员,我仍然认为您会对 MST 作为可视化数据架构的强大功能感兴趣。

以下是 MST 用于驱动图表的示例:

此示例使用 D3 的比例函数,但图表本身只是使用 JSX 中的 SVG 元素渲染的。我不知道任何图表库具有闪烁仓鼠点的选项,因此这是一个很好的例子,说明为什么构建自己的图表很棒——而且它并不像您想象的那么难!

我已经使用 D3 构建图表超过 10 年了,虽然我喜欢它的强大功能,但我总是发现我的代码最终会变得笨拙且难以维护,尤其是在处理复杂的可视化时。MST 通过提供一种优雅的方式将数据处理与渲染分离,彻底改变了这一切。我希望这篇文章能够鼓励您尝试一下。

熟悉 MST 模型

首先,让我们快速概述一下 MST 模型的外观。这不是关于 MST 的所有内容的深入教程。我只想展示基础知识,因为实际上,您在 90% 的时间里只需要这些基础知识。

下面是一个 Sandbox,其中包含使用 MST 构建的简单待办事项列表的代码。快速浏览一下,然后我将解释每个部分的作用。

首先,对象的形状是用模型属性的类型化定义来定义的。简单来说,这意味着待办事项模型的实例必须有一个标题,该标题必须是字符串,并且默认情况下“done”属性为 false。

<code>.model("Todo", {
  title: types.string,
  done: false //这相当于 types.boolean,默认为 false
})</code>

接下来,我们有视图和操作函数。视图函数是根据模型中的数据访问计算值的方法,而不会更改模型保存的数据。您可以将它们视为只读函数。

<code>.views(self => ({
  outstandingTodoCount() {
    return self.todos.length - self.todos.filter(t => t.done).length;
  }
}))</code>

另一方面,操作函数允许我们安全地更新数据。这始终以非可变的方式在后台完成。

<code>.actions(self => ({
  addTodo(title) {
    self.todos.push({
      id: Math.random(),
      title
    });
  }
}));</code>

最后,我们创建一个新的存储实例:

<code>const todoStore = TodoStore.create({
  todos: [
    {
      title: "foo",
      done: false
    }
  ]
});</code>

为了展示存储器的实际作用,我添加了一些控制台日志来显示在触发 Todo 的第一个实例的 toggle 函数之前和之后 outStandingTodoCount() 的输出。

<code>console.log(todoStore.outstandingTodoCount()); // 输出:1
todoStore.todos[0].toggle();
console.log(todoStore.outstandingTodoCount()); // 输出:0</code>

如您所见,MST 为我们提供了一种数据结构,使我们可以轻松地访问和操作数据。更重要的是,它的结构非常直观,代码一目了然——没有 reducer!

让我们创建一个 React 图表组件

好的,现在我们已经了解了 MST 的外观,让我们使用它来创建一个存储器,用于管理图表的 数据。不过,我们将从图表 JSX 开始,因为一旦知道需要哪些数据,构建存储器就容易得多。

让我们看看渲染图表的 JSX。

首先要注意的是,我们正在使用 styled-components 来组织我们的 CSS。如果您不熟悉它,Cliff Hall 有一篇很棒的文章展示了它在 React 应用程序中的使用。

首先,我们正在渲染将更改图表坐标轴的下拉菜单。这是一个相当简单的 HTML 下拉菜单,包装在一个 styled 组件中。需要注意的是,这是一个受控输入,其状态使用我们模型中的 selectedAxes 值设置(稍后我们将介绍这一点)。

<code>
    model.setSelectedAxes(parseInt(e.target.value, 10))
  }
  defaultValue={model.selectedAxes}
></code>

接下来是图表本身。我已经将坐标轴和点拆分为它们自己的组件,这些组件位于单独的文件中。通过保持每个文件简洁,这确实有助于保持代码的可维护性。此外,这意味着如果我们想使用折线图而不是点,我们可以重用坐标轴。这在处理具有多种图表类型的 大型项目时非常有效。它还使单独测试组件变得很容易,无论是在编程上还是在活动样式指南中手动测试。

<code>{model.ready ? (
  <div>
    <axes xlabel="{xAxisLabels[model.selectedAxes]}" xticks="{model.getXAxis()}" ylabel="{yAxisLabels[model.selectedAxes]}" yticks="{model.getYAxis()}"></axes><points points="{model.getPoints()}"></points>
</div>
) : (
  <loading></loading>
)}</code>

尝试在上面的 Sandbox 中注释掉 axes 和 points 组件以查看它们如何独立工作。

最后,我们将组件包装在一个 observer 函数中。这意味着模型中的任何更改都将触发重新渲染。

<code>export default observer(HeartrateChart);</code>

让我们看看 Axes 组件:

如您所见,我们有一个 XAxis 和一个 YAxis。每个都有一个标签和一组刻度标记。我们稍后将介绍如何创建标记,但在这里您应该注意,每个坐标轴都由一组刻度组成,这些刻度是通过遍历一组对象生成的,这些对象具有标签以及 x 或 y 值(取决于我们正在渲染哪个坐标轴)。

尝试更改元素的一些属性值,看看会发生什么……或者会中断什么!例如,将 YAxis 中的线元素更改为以下内容:

<code><line x1="{30}" x2="95%" y1="{0}" y2="{y}"></line></code>

学习如何使用 SVG 构建可视化效果的最佳方法就是简单地进行实验并破坏事物。?

好的,那是图表的一半。现在让我们看看 Points 组件。

图表上的每个点都由两部分组成:一个 SVG 图像和一个圆形元素。图像是动物图标,圆圈提供将鼠标悬停在图标上时可见的脉冲动画。

尝试注释掉图像元素,然后注释掉圆形元素,看看会发生什么。

这次模型必须提供一个点对象数组,该数组为我们提供四个属性:用于在图表上定位点的 x 和 y 值、点的标签(动物的名称)和脉冲,这是每个动物图标的脉冲动画的持续时间。希望这一切看起来都直观且合乎逻辑。

同样,尝试修改属性值以查看哪些更改和中断。您可以尝试将图像的 y 属性设置为 0。相信我,这比阅读 SVG 图像元素的 W3C 规范要容易得多!

希望这能让您了解我们在 React 中如何渲染图表。现在,只需要创建一个具有适当操作的模型来生成我们在 JSX 中循环所需的数据即可。

创建我们的存储器

这是存储器的完整代码:

我将代码分解成前面提到的三个部分:

  1. 定义模型的属性
  2. 定义操作
  3. 定义视图

定义模型的属性

我们在这里定义的所有内容都可以从外部作为模型实例的属性访问,并且——如果使用可观察的包装组件——对这些属性的任何更改都将触发重新渲染。

<code>.model('ChartModel', {
  animals: types.array(AnimalModel),
  paddingAndMargins: types.frozen({
    paddingX: 30,
    paddingRight: 0,
    marginX: 30,
    marginY: 30,
    marginTop: 30,
    chartHeight: 500
  }),
  ready: false, // 表示 types.boolean,默认为 false
  selectedAxes: 0 // 表示 types.number,默认为 0
})</code>

每种动物都有四个数据点:名称 (Creature)、寿命 (Longevity__Years_)、重量 (Mass__grams_) 和静息心率 (Resting_Heart_Rate__BPM_)。

<code>const AnimalModel = types.model('AnimalModel', {
  Creature: types.string,
  Longevity__Years_: types.number,
  Mass__grams_: types.number,
  Resting_Heart_Rate__BPM_: types.number
});</code>

定义操作

我们只有两个操作。第一个 (setSelectedAxes) 在更改下拉菜单时调用,它更新 selectedAxes 属性,该属性反过来决定使用哪些数据来渲染坐标轴。

<code>setSelectedAxes(val) {
  self.selectedAxes = val;
},</code>

setUpScales 操作需要更多解释。此函数在图表组件挂载后立即在 useEffect hook 函数中调用,或在窗口大小调整后调用。它接受一个对象,其中包含包含该元素的 DOM 的宽度。这使我们可以为每个坐标轴设置比例函数以填充完整的可用宽度。我将很快解释比例函数。

为了设置比例函数,我们需要计算每种数据类型的最大值,因此我们首先遍历动物来计算这些最大值和最小值。我们可以将零用作我们想要从零开始的任何比例的最小值。

<code>// ...
self.animals.forEach(
  ({
    Creature,
    Longevity__Years_,
    Mass__grams_,
    Resting_Heart_Rate__BPM_,
    ...rest
  }) => {
    maxHeartrate = Math.max(
      maxHeartrate,
      parseInt(Resting_Heart_Rate__BPM_, 10)
    );
    maxLongevity = Math.max(
      maxLongevity,
      parseInt(Longevity__Years_, 10)
    );
    maxWeight = Math.max(maxWeight, parseInt(Mass__grams_, 10));
    minWeight =
      minWeight === 0
        ? parseInt(Mass__grams_, 10)
        : Math.min(minWeight, parseInt(Mass__grams_, 10));
  }
);
// ...</code>

现在设置比例函数!在这里,我们将使用 D3.js 的 scaleLinear 和 scaleLog 函数。在设置这些函数时,我们指定域,它是函数可以预期的最小和最大输入,以及范围,它是最大和最小输出。

例如,当我使用 maxHeartrate 值调用 self.heartScaleY 时,输出将等于 marginTop。这是有道理的,因为这将位于图表的顶部。对于寿命属性,我们需要有两个比例函数,因为此数据将显示在 x 轴或 y 轴上,具体取决于选择的哪个下拉选项。

<code>self.heartScaleY = scaleLinear()
  .domain([maxHeartrate, minHeartrate])
  .range([marginTop, chartHeight - marginY - marginTop]);
self.longevityScaleX = scaleLinear()
  .domain([minLongevity, maxLongevity])
  .range([paddingX   marginY, width - marginX - paddingX - paddingRight]);
self.longevityScaleY = scaleLinear()
  .domain([maxLongevity, minLongevity])
  .range([marginTop, chartHeight - marginY - marginTop]);
self.weightScaleX = scaleLog()
  .base(2)
  .domain([minWeight, maxWeight])
  .range([paddingX   marginY, width - marginX - paddingX - paddingRight]);</code>

最后,我们将 self.ready 设置为 true,因为图表已准备好渲染。

定义视图

我们有两组视图函数。第一组输出渲染坐标轴刻度所需的数据(我说我们会到那里!)第二组输出渲染点所需的数据。我们将首先查看刻度函数。

只有两个从 React 应用程序调用的刻度函数:getXAxis 和 getYAxis。这些函数只是根据 self.selectedAxes 的值返回其他视图函数的输出。

<code>getXAxis() {
  switch (self.selectedAxes) {
    case 0:
      return self.longevityXAxis;
      break;
    case 1:
    case 2:
      return self.weightXAxis;
      break;
  }
},
getYAxis() {
  switch (self.selectedAxes) {
    case 0:
    case 1:
      return self.heartYAxis;
      break;
    case 2:
      return self.longevityYAxis;
      break;
  }
},</code>

如果我们查看 Axis 函数本身,我们可以看到它们使用比例函数的 ticks 方法。这将返回适合坐标轴的一组数字。然后,我们遍历这些值以返回坐标轴组件所需的数据。

<code>heartYAxis() {
  return self.heartScaleY.ticks(10).map(val => ({
    label: val,
    y: self.heartScaleY(val)
  }));
}
// ...</code>

尝试将 ticks 函数的参数值更改为 5,看看它如何影响图表:self.heartScaleY.ticks(5)。

现在我们有了返回 Points 组件所需数据的视图函数。

如果我们查看 longevityHeartratePoints(它返回“寿命与心率”图的点数据),我们可以看到我们正在遍历动物数组并使用适当的比例函数来获取点的 x 和 y 位置。对于脉冲属性,我们使用一些数学方法将心率的每分钟跳动次数转换为表示单个心跳以毫秒为单位的持续时间的数值。

<code>longevityHeartratePoints() {
  return self.animals.map(
    ({ Creature, Longevity__Years_, Resting_Heart_Rate__BPM_ }) => ({
      y: self.heartScaleY(Resting_Heart_Rate__BPM_),
      x: self.longevityScaleX(Longevity__Years_),
      pulse: Math.round(1000 / (Resting_Heart_Rate__BPM_ / 60)),
      label: Creature
    })
  );
},</code>

在 store.js 文件的末尾,我们需要创建一个 Store 模型,然后使用动物对象的原始数据实例化它。通常的做法是将所有模型附加到父 Store 模型,然后可以根据需要通过顶层提供程序访问这些模型。

<code>const Store = types.model('Store', {
  chartModel: ChartModel
});
const store = Store.create({
  chartModel: { animals: data }
});
export default store;</code>

就是这样!这是我们的演示:

这绝不是在 JSX 中组织数据以构建图表的唯一方法,但我发现它非常有效。我已经在实际环境中使用这种结构和堆栈为大型企业客户构建了一个自定义图表库,并且对 MST 在此目的中的出色表现感到震惊。我希望您也能有同样的体验!

以上是制作图表?尝试使用MOBX状态树为数据供电的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
在保留边框半径的同时,扩展盒子的各种方法在保留边框半径的同时,扩展盒子的各种方法Apr 17, 2025 am 11:19 AM

我最近注意到Codepen的一个有趣的更改:在悬停在主页上的笔时,有一个矩形,圆角在后面扩展。

每周平台新闻:改进慢速连接上的UX,用于编写ALT文本的提示和HTML加载属性的多填充每周平台新闻:改进慢速连接上的UX,用于编写ALT文本的提示和HTML加载属性的多填充Apr 17, 2025 am 11:09 AM

在本周的综述中,如何确定慢速连接,我们应该在图像中放入alt文本中的内容以及用于HTML加载属性的新polyfill,

可重复使用的弹出式添加一点流行音乐可重复使用的弹出式添加一点流行音乐Apr 17, 2025 am 11:02 AM

弹出窗口是一种瞬态视图,当用户单击控制按钮或定义区域内时,屏幕上的内容顶部显示在屏幕上。例如,

带有真实下划线的造型链接带有真实下划线的造型链接Apr 17, 2025 am 10:57 AM

在进入如何样式下划线之前,我们应该回答以下问题:我们应该强调吗?

每周平台新闻:HTML加载属性,主要的ARIA规格以及从iframe转移到Shadow dom每周平台新闻:HTML加载属性,主要的ARIA规格以及从iframe转移到Shadow domApr 17, 2025 am 10:55 AM

在本周的平台新闻综述中,Chrome引入了一个用于加载的新属性,Web开发人员的可访问性规范以及BBC Move

带有GraphQL的多人游戏TIC TAC TOE带有GraphQL的多人游戏TIC TAC TOEApr 17, 2025 am 10:54 AM

GraphQL是API的查询语言,对前端开发人员非常有能力。正如GraphQL网站所解释的那样,您可以描述您的数据,询问什么

使用CSS变量的逻辑操作使用CSS变量的逻辑操作Apr 17, 2025 am 10:44 AM

通常,在使用开关变量(一个0或1的变量时,这是本文中更详细地解释的概念),我希望我可以

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它们
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中