Highcharts 图表中图例显示状态存储的功能设计详解
作者:麦索 发布时间:2023-05-30 02:01:09
需求背景
公司前端使用 Highcharts 构建图表,图表的图例支持点击显示或隐藏相应的指标。现在有需求后端需要存储用户在前端点击后显示图表的状态,对于已经隐藏的图例相应的指标线下次进入页面后依然隐藏。
这样做的目的是简化图表信息和去除噪音,有些信息对于某些用户来说是不太关心的,用户取消显示它们是合理的。
需求方案
方案一
使用数据库枚举值,将图表的存储状态枚举。这是一种实现上比较简单的方案,但是其组合较多,图例越多所需要添加的枚举值就越多,原本的图表状态中有两个图例可以进行此操作,因此使用了这种方案。
现在需要将这个功能扩展到 4 个图表,未来又可能扩展到更多图表,所以这种简单的方案已经不适用了。
方案二
使用一个数据库 JSON 类型字段存储图例的是否显示的状态,其结构类似下面这样:
{
"chart_1":{
"legend_1":true,
"legend_2":true,
"legend_3":true,
"legend_4":true,
"legend_5":true,
"legend_6":true,
"legend_7":false,
"legend_8":false
},
"chart_2":{
"legend_1":true,
"legend_2":true,
"legend_3":true,
"legend_4":true,
"legend_5":true
},
"chart_3":{
"legend_1":true,
"legend_2":true,
"legend_3":true,
"legend_4":true
},
"chart_3":{
"legend_1":true,
"legend_2":true,
"legend_3":true,
"legend_4":true,
"legend_5":true,
"legend_6":true,
"legend_7":true
}
}
这样使用一个字段将所有图表的图例状态都在一个地方维护,而且避免了过多枚举值的问题。但在实际项目中因为每个用户都有这样一个值需要在数据库中维护,所以它会占用大量的数据库存储空间,在工程质量和未来成本控制上与工程目标不符。
方案三
对方案二进行改进,存储给每个图表存储一个 int 值,实际这个 int 值可以在代码中转换成二进制,用每个二进制值位的 0 和 1 来标示图例是否该显示。其存储结构如下:
{
"chart_1": 15,
"chart_2": 31,
"chart_3": 127,
"chart_3": 252
}
同时对于大部分用户而言很可能是不会对默认图表做出更改,那么我们可以在代码中维护一个默认值,当数据库中相应值与默认值一致时,无需实际存储到数据库中,在用户获取信息时直接返回默认值即可。
代码实现
后端使用的是 PHP 的 Laravel 框架,首先在相应的 Model 里我新增了一些类似下面的常量:
public const BITMAP_INDEXES_BY_LEGEND_IDENTIFIER_BY_CHART_NAME = [
'chart_1' => [
'legend_1' => 7,
'legend_2' => 6,
'legend_3' => 5,
'legend_4' => 4,
'legend_5' => 3,
'legend_6' => 2,
'legend_7' => 1,
'legend_8' => 0,
],
'chart_2' => [
'legend_1' => 3,
'legend_2' => 2,
'legend_3' => 1,
'legend_4' => 0,
],
'chart_3' => [
'legend_1' => 6,
'legend_2' => 5,
'legend_3' => 4,
'legend_4' => 3,
'legend_5' => 2,
'legend_6' => 1,
'legend_7' => 0,
],
'chart_4' => [
'legend_1' => 4,
'legend_2' => 3,
'legend_3' => 2,
'legend_4' => 1,
'legend_5' => 0,
],
];
// Each bit of a bitmap is like a bucket, which stores the displayed or hidden state.
public const DEFAULT_CHART_LEGEND_VISIBILITIES = [
'chart_1' => 0b1111,
'chart_2' => 0b11111,
'chart_3' => 0b1111111,
'chart_4' => 0b11111100,
];
BITMAP_INDEXES_BY_LEGEND_IDENTIFIER_BY_CHART_NAME 维护的是每个图例在 bitmap 中的存储位置,这样在图例增加时只需维护相应的 chart 的存储位置即可,不会影响到其他图表的图例存储。
DEFAULT_CHART_LEGEND_VISIBILITIES 定义了图例的默认值,当用户未做更改时可以直接返回其值,在存储时可进行比对,如果值一致就不存储到数据库里了。
在定义完这两个常量后,需要做的就是对存储和获取的过程给予相应的处理,Laravel 的 Model 提供了访问器和设置器的功能,我可以利用这个机制对值进行处理,因此我在 model 里新增了以下方法:
/**
* @return array<string,array<string,bool>>
*/
public function getChartLegendVisibilitiesAttribute(?string $value): array
{
$value = $value !== null ? \Safe\json_decode($value, true) : [];
$value += self::DEFAULT_CHART_LEGEND_VISIBILITIES;
$chart_legend_visibilities = [];
/** @var string $chart_name */
foreach ($value as $chart_name => $legend_visibilities_bitmap) {
foreach (self::BITMAP_INDEXES_BY_LEGEND_IDENTIFIER_BY_CHART_NAME[$chart_name] as $legend_identifier => $bitmap_index) {
$chart_legend_visibilities[$chart_name][$legend_identifier] = (bool) (($legend_visibilities_bitmap >> $bitmap_index) & 1);
}
}
ksort($chart_legend_visibilities);
return $chart_legend_visibilities;
}
/**
* @param array<string,array<string,bool>>|null $value
*
* @return void
*/
public function setChartLegendVisibilitiesAttribute(?array $value): void
{
if ($value !== null) {
$chart_legend_visibilities = [];
foreach ($value as $chart_name => $legend_visibilities) {
$legend_visibilities_bitmap = 0;
foreach ($legend_visibilities as $legend_identifier => $legend_visibility) {
$bitmap_index = self::BITMAP_INDEXES_BY_LEGEND_IDENTIFIER_BY_CHART_NAME[$chart_name][$legend_identifier];
$legend_visibilities_bitmap |= ($legend_visibility & true) << $bitmap_index;
}
if ($legend_visibilities_bitmap !== self::DEFAULT_CHART_LEGEND_VISIBILITIES[$chart_name]) {
$chart_legend_visibilities[$chart_name] = $legend_visibilities_bitmap;
}
}
$value = $chart_legend_visibilities === [] ? null : \Safe\json_encode($chart_legend_visibilities);
}
$this->attributes[self::COLUMN_CHART_LEGEND_VISIBILITIES] = $value;
}
这样我所要实现的功能基本在这里完成了。现在所要做的就是在控制器里对返回的图表数据的状态进行绑定:
/**
* @param array<string,array<int|string,mixed>> $chart_data
* @param \App\Models\UserExtra $user_extra
* @param string $chart_name
*
* @return array<string,array<int|string,mixed>>
*/
private static function getChartLegendVisibility(array $chart_data, UserExtra $user_extra, string $chart_name): array
{
$legend_label_id_by_store_index = array_flip(UserExtra::BITMAP_INDEXES_BY_LEGEND_IDENTIFIER_BY_CHART_NAME[$chart_name]);
$legend_visibilities = $user_extra->chart_legend_visibilities[$chart_name];
foreach ($chart_data['series'] as $series_index => $series) {
$key = array_shift($legend_label_id_by_store_index);
$chart_data['series'][$series_index]['id'] = $key;
$chart_data['series'][$series_index]['visible'] = $legend_visibilities[$key];
}
return $chart_data;
}
$chart_data 传递的是构建好的 Highcharts 需要的数据,这部分可以参考 Highcharts 的文档。这个函数的目的就是将每个图例的数据与我们所维护的图例是否显示的数据绑定,之后再提供修改图例显示状态的 API 即可实现这个功能。
来源:https://juejin.cn/post/7209950095403909176
猜你喜欢
- 同伪类的方式类似,伪元素通过对插人到文档中的虚构元素进行触发,从而达到某种效果。在CSS1里,有两个伪元素,即:first-letter和f
- 用IIS调试ASP程序时,有的页面可以显示出错行及出错原因,虽然原因不是很具体但足以引导调试程序,但有些时候就直接出现:HTTP 500 -
- 目的: 从数据库读取二进制位图图形数据资料, 透过 ImageMagickObject 组件即时制作缩略图,并显示在网页上 (ge
- 划动门菜单技术:运行代码框<style>body {font-size:12px;font-family:宋体}ul.TabBa
- (以下内容部分内容参考了http://adomas.org/javascript-mouse-wheel/ )之前js 仿Photoshop
- 如何用Response.Write调用代替内嵌表达式?我们可以利用下面的代码,注意:代码的每一行对响应流有一次写操作,所有的代码都包含在一个
- 页面加载loading效果, 这个挺好玩的!用setTimeout实现的!可以和服务端整合弄一些生成HTML或者上传文件等应用!
- 一图胜“十”言:SQL Server 数据库总结 一个大概的总结 经过一段时间的学习,也对数据库有了一些认识。 数据库基本是由表,关系,操作
- 最近在研究品牌如何演绎,当然,看的时候没有忘记本行,分析了一下他们的交互设计~~路易威登LV上图采用胶片展示多组信息——大片展示品牌渲染。利
- DROP PRIMAY DEY用于取消主索引。注释:在MySQL较早的版本中,如果没有主索引,则DROP PRIMARY KEY会取消表中的
- 导语:近年来,全世界都纷纷投身网络热潮。从小企业到大公司,再到网络学校和大学,大家都在努力提升自己的网络影响力,这样既免费为自身品牌做广告,
- 根据"客服果果"的"十几行的超简日历组件"http://bbs.51js.com/viewthrea
- 在ASP.net页面中,我们编写JavaScript脚本附加有注释时,这些注释也往往会随JavaScript脚本一起送到客户端。
- 1. 你必须有自己的服务器,可以在服务器上建立站点。2. 域名管理里 你的域名必须支持泛解析。(现在好像除了 双线双I
- 网页制作中用到的特效字,你一定是用图象处理软件制作的吧!告诉你,不用图象处理软件,我也能做出漂亮的特效字来,你看,阴影字我就是这样做出来的。
- 在设计网页之前,客户或产品经理会提出对网页视觉风格设计的期望:活跃、大气、稳重、信赖、都市化….. 设计师一听到关键词或许很自然地在心里蹦出
- Google Talk是一个功能很简洁的即时通讯工具,尤其是它的文字输入区域,不同于其他IM,除了一个文字输入区域外没有任何其他操作。但是用
- 数据库设计(Database Design)的概念:数据库设计是指对于一个给定的应用环境,构造最优的数据库模式,建立数据库及其应用系统,使之
- 1.指向“开始->程序->Microsoft SQL Server 2005->配置工具->SQL Server 外
- 做程序开发的人都知道版本控制的重要性, 代码的管理好说,TFS/SVN/VSS/CVS,哪个都能用。但涉及到数据库的版本控制,就不是太好做的