字节前端分享|酷炫的可视化大屏代码开源了!

技术

picture.image

在大屏产品中,可视化扮演着信息展示和传达、用户体验和互动、数据分析和决策支持、品牌展示和差异化、故事叙述和信息呈现等至关重要的角色。作为可视化图表的重要载体之一,大屏与智能BI产品不管是在产品设计,还是可视化设计的侧重点都有很大不同。本文以火山引擎DataWind产品数据大屏为例,为您揭示如何建设令人叹为观止的数据大屏。(文章展现的大屏设计及相关数据均为演示模型)

picture.image

智能BI产品

picture.image

picture.image

为不同行业的数据大屏使用不同的颜色主题可以提高数据可视化效果、增强数据传达的意义、提高品牌识别度和满足用户需求,从而更好地呈现数据。

图表库能够支持场景化的主题色彩配置,这意味着用户可以根据不同的行业需求来选择不同的主题色彩,以更好地呈现数据。在不同的行业中,用户对于数据可视化的需求和期望可能会有所不同,因此场景化的主题色彩配置可以帮助用户更好地满足其特定的需求。

例如,在金融行业中,用户可能更注重数据的准确性和可靠性,因此金融行业的图表库可能需要提供更加严肃和专业的主题色彩配置;而在广告行业中,用户更注重图表的视觉效果和吸引力,因此广告行业的图表库可能需要提供更加鲜艳和夸张的主题色彩配置。

/ 不同场景下的案例效果 /

1.分析场景

picture.image

2.金融场景

picture.image

3.文旅场景

picture.image

/ 实现揭秘 /

从上述案例中,我们可以注意到大屏可视化色彩设计有两个明显的特点:1、行业相关联的颜色主题;2、图元渐变着色。

  1. 颜色主题注册和切换

主题色板的构造基于于语义化及美观设计原则,即结合使用场景(保证大屏主题的场景表现力)、配色公式(保证图元在美观度、差异度等方面的配色效果和信息表达力)等逻辑进行设计。而针对大屏业务场景,我们沉淀出一套色彩方案,涵盖党建、金融、科技、文旅等行业,结合 VChart 主题注册和切换能力,做到开箱即用。

picture.image picture.image

https://github.com/VisActor/VChart/blob/develop/docs/assets/themes/colors.json


          
const response = await fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json');
          
const colorTheme = await response.json();
          

          
// 注册主题
          
const theme = {};
          
for (const colorKey in colorTheme) {
          
  const colorName = colorTheme[colorKey].name;
          
  theme[colorName] = {
          
    colorScheme: {
          
      default: colorTheme[colorKey].colors
          
    }
          
  };
          
  VChart.ThemeManager.registerTheme(colorKey, theme[colorName]);
          
}
          

          
// 主题切换
          
  VChart.ThemeManager.setCurrentTheme('volcanoBlue');
      

在线示例:

https://www.visactor.io/vchart/guide/tutorial\_docs/Theme/Customize\_Theme

/ 渐变效果实现 /

纯色到渐变色的转换:纯色 => 图元填充渐变 + 图元描边边渐变。

picture.image

picture.image

picture.image

picture.image

示例地址: https://codesandbox.io/s/bar-gradient-ycr8m8

核心代码:


          
const gradientCallback = (datum, ctx, type) => {
          
  return {
          
    gradient: "linear",
          
    x0: 0,
          
    y0: 0,
          
    x1: 0,
          
    y1: 1,
          
    stops: [
          
      {
          
        offset: 0,
          
        fillOpacity: 0,
          
        color: hexToRgba(ctx.seriesColor(datum.type), 1),
          
      },
          
      {
          
        offset: 1,
          
        fillOpacity: 1,
          
        color: hexToRgba(ctx.seriesColor(datum.type), 0),
          
      }
          
    ]
          
  };
          
};
          

          
// 以同样的方式在主题中注册和切换
          
const theme = {
          
    series: {
          
      bar: {
          
        bar: {
          
          style: {
          
            fill: (datum, ctx) => gradientCallback(datum, ctx, "fill"),
          
            stroke: (datum, ctx) => gradientCallback(datum, ctx, "stroke"),
          
            lineWidth: 2
          
          }
          
        }
          
      }
          
    }
          
}
          

          
VChart.ThemeManager.registerTheme(theme, 'gradient');
          
VChart.ThemeManager.setCurrentTheme('gradient');
      

渐变 色详细说明参考:

https://www.visactor.io/vchart/guide/tutorial\_docs/Chart\_Concepts/Series/Mark

/ 最终效果 /

最终呈现的图表视觉效果不管是在提高吸引力,引流观众方面,还是在提升场景辨识度上都颇具成效。

picture.image

picture.image

/ 在线Demo /

picture.image

示例地址:

https://www.visactor.io/vchart/demo/theme/theme-style

更多主题教程见:

https://www.visactor.io/vchart/guide/tutorial\_docs/Theme/Theme

picture.image

在图表图元上做细微的装饰不仅可以提高视觉吸引力,增加图表的美感和吸引力,使得读者更愿意阅读和理解数据;更重要地,它还可以增加品牌识别度,帮助提高品牌识别度和专业感,使得数据大屏更具个性化和品牌化。

在图表中,图元负责实现数据到图形的映射,比如:数值的大小映射为矩形的高度,数值的类型映射为矩形的颜色等。而组件则负责数据的数值标记、图元的交互,比如:坐标轴以标签和刻度的形式标记某个高度对应的具体数值大小。

辅助装饰通常围绕着图元和组件展开,对图元的辅助装饰负责突出数据,对组件的装饰则负责个性化展示。

/ 实现揭秘 /

1.图元装饰

由于装饰的位置强依赖于图元,在大屏侧无法准确定位并添加。通过VChart提供 拓展Mark 配置能力可以直接渲染出依附于既有图元的装饰图元,完美解决这一问题。

picture.image

示例地址:

https://codesandbox.io/s/line-with-halo-j54hv8

picture.image

示例地址:

https://codesandbox.io/s/line-with-halo-forked-xccmvq?file=/src/index.ts


          
extensionMark: [
          
    {
          
      name: "markSymbol",
          
      type: "symbol",
          
      dataId: "data", // 绑定的数据id
          
      visible: true,
          
      style: {
          
        x: (datum, ctx, elements, dataView) => {
          
          return ctx.valueToX([datum["beinirRbfVnf"]]); // 自定义x映射
          
        },
          
        y: (datum, ctx, elements, dataView) => {
          
          return ctx.valueToY([datum["10002"]]); // 自定义y映射
          
        },
          
        size: 13,
          
        fillOpacity: 0.1,
          
        fill: "#FFF",
          
        strokeOpacity: 0.5,
          
        lineWidth: 1,
          
        stroke: {
          
          gradient: "conical",
          
          startAngle: 0,
          
          endAngle: 6.283185307179586,
          
          stops: [
          
            {
          
              offset: 0,
          
              color: "rgba(255, 255, 255, 0)"
          
            },
          
            {
          
              offset: 1,
          
              color: "rgba(255, 255, 255, 1)"
          
            }
          
          ]
          
        }
          
      }
          
    }
          
  ]
      

通过 VChart 自定义渲染能力,还可以支持更多图元的纹理装饰。

picture.image

picture.image

2.组件装饰

为了标记出坐标轴的覆盖范围,我们需要增加轴辅助装饰。用VChart的轴tick回调函数可以实现这一效果。实现原理是,根据回调中的index判断tick是否是第一个或最后一个,如果是的话则设置为可见,不是的话则隐藏。

picture.image

示例地址: https://codesandbox.io/s/axes-with-tick-sign-9n9jtf

核心代码:


          
axes: [{
          
  // ...
          
  tick: {
          
    size: 6,
          
    visible: true,
          
    style: (...args: any[]) => { // args[1]为tick index, args[3]为tick全量数据
          
      tickCount = args[3].length - 1;
          
      return {
          
        lineWidth: args[1] === 0 || args[1] === tickCount ? 2 : 0,
          
        stroke: "rgb(0,110,255)"
          
      };
          
    }
          
  }
          
}]
          

      

picture.image

从数据场景而言,大屏通常用于展示实时数据、动态信息和即时反馈。通过动态效果,可以更好地呈现和展示这些数据的动态变化和趋势,使观众能够及时了解最新的数据情况。

从现实场景而言,大屏通常在公共场所或会议展览等场合使用,需要通过瞬间的视觉冲击来吸引人们的注意,使他们停下来观看。

picture.image

/ 不同图表的动画效果 /

设计动画的前提是明确动画的目标,不同的目标可能需要不同类型、频率和复杂程度的动画实现。显而易见,贯穿大屏数据可视化场景的动画目标如下:

  1. 强调数据重点和变换,通过设计合适的过渡和动作,可以使关键数据或信息在动画中突出显示。重点和关键变化的动画应该被放在视觉的焦点位置,使其更易于观察和理解。
  2. 吸引观众的注意,通过炫酷的动态效果可以迅速抓人眼球,但同时又需要控制速度和流畅度,以免影响观感。

根据目标在大屏中可以总结出数据更新动画、高亮动画和氛围动画,不同图元的动画效果各不相同。

picture.image

柱图数据更新动画

示例地址:

https://codesandbox.io/s/animation-bar-yypwgs?file=/src/index.ts

picture.image

柱图氛围动画

示例地址:

https://www.visactor.io/vchart/guide/tutorial\_docs/extend/custom\_animation

picture.image

面积图数据更新动画

示例地址:

https://codesandbox.io/s/animation-line-6nlpd4?file=/src/index.ts

除上述图表外,还有饼图、散点图等基本图表类型对应的动画,在此不一一赘述。

/ 实现揭秘 /

VChart动画的实现依赖于VRender绘图引擎与VGrammar可视化语法。从实现分工而言,VRender提供任意图形的形变能力,VGrammar负责控制形变动画的流程,VChart进行上层封装并将配置以简洁易用的方式暴露给用户。

对于上述动画,VChart层的实现主要依赖于对 VGrammar动画语法的封装

1. 柱图数据更新动画

picture.image

示例地址:

https://codesandbox.io/s/bar-update-animation-7jkd3j?file=/src/index.ts

核心代码:


          
animationUpdate: {
          
     type: 'moveIn',
          
    duration: 500
          
  }
      

2. 柱图数据高亮动画

picture.image

示例地址:

https://codesandbox.io/s/animation-highlight-j6d4f2?file=/src/index.ts

核心代码:


          
animationNormal: {
          
    bar: [
          
      {
          
        loop: true,
          
        startTime: 100,
          
        oneByOne: 100,
          
        timeSlices: [
          
          {
          
            delay: 1000,
          
            effects: {
          
              channel: {
          
                fillOpacity: { to: 0.5 }
          
              },
          
              easing: "linear"
          
            },
          
            duration: 500
          
          },
          
          {
          
            effects: {
          
              channel: {
          
                fillOpacity: { to: 1 }
          
              },
          
              easing: "linear"
          
            },
          
            duration: 500
          
          }
          
        ]
          
      }
          
    ]
          
  }
      

3.柱图氛 围动画

picture.image

示例地址:

https://www.visactor.io/vchart/guide/tutorial\_docs/extend/custom\_animation

核心代码:


          
animationNormal: {
          
    bar: {
          
      loop: 100,
          
      duration: 1500,
          
      easing: 'quadIn',
          
      custom: VRender.StreamLight,
          
      customParameters: {
          
        attribute: {
          
          fillColor: '#bcdeff',
          
          opacity: 0.3,
          
          blur: 20,
          
          shadowColor: '#bcdeff',
          
          width: 160
          
        }
          
      }
          
    }
          
}
      

picture.image

除了提供多种预定义的图表类型,如柱状图、折线图、饼图等,我们还支持用户根据自己的数据特点和展示需求,创建和自定义各种类型的图表。使用VGranmar图形语法,你可以完成数据到图形的自定义映射,画布的自定义布局,动画效果和流程的自定义编排以及交互功能的自定义配置。

比如在大屏新增的排行榜组件并非VChart既有图表类型,而是通过图形语法VGrammar完全自定义实现。

/ 实现揭秘 /

1.自定义映射

首先,要区分构成排行榜需要的图元类型,它们分别是矩形、标题、标签、装饰点。其次,需要确定图元的属性与数据的对应关系。

以如下数据为例:


          
const data = [
          
  { category: '吉林', value: 50 },
          
  { category: '内蒙古', value: 40 },
          
  { category: '河北', value: 30 },
          
  { category: '湖南', value: 30 },
          
  { category: '江西', value: 24 },
          
]
      

图元及数据与数据的映射关系:

| 图元类型 | 属性 | 数据字段 | | 矩形 | x | 与数据无关 | | y | category | | width | value | | 标题 | x | 与数据无关 | | y | category | | text | category | | 标签 | x | 与数据无关 | | y | category | | text | value | | 装饰点 | x | value | | y | category | | size | 与数据无关 |

映射结果:

picture.image

在线示例:

https://codesandbox.io/s/vgrammar-ranking-list-animation-dr87sy

核心代码:


          
marks: [
          
      // 矩形
          
      {
          
        type: 'rect',
          
        from: { data: chartSpec.data[0].id },
          
        dependency: ['viewBox', 'xScale', 'yScale'],
          
        encode: {
          
          update: (datum, element, params) => {
          
            return {
          
              x: params.xScale.scale(dataMin), // 根据xScale做数据映射计算
          
              y: params.yScale.scale(datum['category']), // 根据yScale做数据映射计算
          
              width: params.xScale.scale(datum['value']), // 根据xScale做数据映射计算
          
              height: barWidth,
          
              // ...省略其他属性
          
            }
          
          },
          
        },
          
      },
          
      // 装饰点
          
      {
          
        type: 'symbol', 
          
        from: { data: chartSpec.data[0].id },
          
        dependency: ['viewBox', 'yScale', 'xScale'],
          
        encode: {
          
          update: (datum, element, params) => {
          
            return {
          
              x: params.xScale.scale(replaceNilDatum(datum, 'value', dataMin)),
          
              y: params.yScale.scale(datum['category']),
          
              // ...省略其他属性
          
            }
          
          },
          
        },
          
      },
          
      // 标题
          
      {
          
        type: 'text',
          
        from: { data: chartSpec.data[0].id },
          
        dependency: ['viewBox', 'yScale', 'xScale'],
          
        encode: {
          
          update: (datum, element, params) => {
          
            return {
          
              text: leftTextFormatMethod(datum['category']),
          
              x: params.xScale.scale(dataMin),
          
              y: params.yScale.scale(datum['category']),
          
              // ...省略其他属性
          
            }
          
          },
          
        },
          
      },
          
      // 标签
          
      {
          
        type: 'text',
          
        from: { data: chartSpec.data[0].id },
          
        dependency: ['viewBox', 'yScale'],
          
        encode: {
          
          update: (datum, element, params) => {
          
            return {
          
              text: rightTextFormatMethod(datum['value']),
          
              x: params.viewBox.x2,
          
              y: params.yScale.scale(datum['category']),
          
              // ...省略其他属性
          
            }
          
          },
          
        },
          
      },
          
    ],
      

2. 自定义动画

排行榜组件的动画分为:入场动画、数据更新动画和退场动画。

入场动画时,所有元素的y属性从画布外,变为正常状态。

picture.image

数据更新时,矩形图元的width属性从0变为正常状态。

picture.image

退场动画时,所有元素的y属性从正常状态变为画布外。

picture.image

在线示例:

https://codesandbox.io/s/vgrammar-ranking-list-animation-dr87sy

核心代码:


          
// 以矩形图元为例
          

          
// 入场动画
          
enter: [
          
    {
          
      delay: 0,
          
      duration: durationTime,
          
      channel: {
          
        dy: {
          
          from: (datum, element, params) => {
          
            return params.viewBox.y2
          
          },
          
          to: 0,
          
        },
          
      },
          
    },
          
  ],
          
  
          
  // 更新动画
          
  enter: [
          
    {
          
      delay: 0,
          
      duration: durationTime,
          
      channel: {
          
        width: {
          
          from: 0,
          
          to: (datum, element, params) => {
          
            return params.xScale.scale(datum['value'])
          
          }
          
        }
          
      }
          
  ],
          
  
          
  // 退场动画
          
  exit: [
          
    {
          
      delay: 0,
          
      duration: durationTime,
          
      channel: {
          
        dy: {
          
          from: 0,
          
          to: (datum, element, params) => {
          
            return params.viewBox.y2
          
          },
          
        },
          
      },
          
    },
          
  ],
      

3.自定义交互

在VChart中,每个内置图表都有对应的图元点击事件用于数据联动。为了对齐这个能力,自定义图表排行榜也需要增加图元点击事件。依赖于vgrammr的事件注册能力,只需要在实例上调用 addEventListener 即可实现。

picture.image

在线示例:

https://codesandbox.io/s/vgrammar-ranking-list-animation-dr87sy

核心代码:


        
            

          this.chartInstance.addEventListener('pointerdown', this.clickEventHandler)
        
      

picture.image

本文以 DataWind 数据大屏产品为例,详细介绍了实现一个优秀的数据大屏在技术和设计上要考虑的内容和实现方法,希望对您有所启发。

产品介绍

火山引擎智能数据洞察DataWind

是一款支持大数据明细级别自助分析的增强型 ABI 平台。从数据接入、数据整合,到查询、分析,最终以数据门户、数字大屏、管理驾驶舱的可视化形态呈现给业务用户,让数据发挥价值。后台回复数字“5”了解产品

--推荐阅读--

picture.image

picture.image

picture.image

picture.image

picture.image

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论