15、数据可视化与动画效果实现

数据可视化与动画效果实现

在数据可视化和动画效果实现的领域,有许多强大的工具和技术可供我们使用。本文将介绍几种不同的实现方法,包括模拟随机变化的仪表、创建动画 3D 图表、使用 flotJS 进行时间序列图表绘制以及使用 RaphaelJS 构建时钟。

1. 模拟随机变化的仪表

如果想要让仪表值随机上下变化,模拟驾驶速度的变化,可以使用以下代码:

}else{
    meterValue += 1 - Math.random() * 2;
    meterValue = Math.max(0, Math.min(meterValue, 120)); // 确保值在 0 到 120 之间
}

上述代码会随机地给仪表值加上一个 -1 到 1 之间的值,并且通过 Math.max Math.min 函数确保值始终在 0 到 120 的范围内。

2. 创建动画 3D 图表(canvas3DGraph)

这是一个基于 Dragan Bajcic 源代码的有趣示例,可用于创建自定义的 3D 数据可视化。

2.1 准备工作

若要获取原始源代码文件,可访问 链接

2.2 实现步骤
  1. 创建 HTML 文件
<!DOCTYPE html>
<html>
  <head>
    <title>canvas3DGraph.js</title>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="./external/dragan/canvas3DGraph.css">
    <script src="./external/dragan/canvas3DGraph.js"></script>
    <script src="./07.02.3d.js"></script>
  </head>
  <body style="background:#fafafa">
    <div id="g-holder">
      <div id="canvasDiv">
        <canvas id="graph" width="600" height="600"></canvas>
        <div id="gInfo"></div>
      </div>
    </div>
  </body>
</html>
  1. 创建 CSS 文件(canvas3DGraph.css)
#g-holder {
    height:620px;
    position:relative;
}

#canvasDiv{
    border:solid 1px #e1e1e1;
    width:600px;
    height:600px;
    position:absolute;
    top:0px; left:0px;
    z-index:10;
}
#x-label{
    position:absolute;
    z-index:2;
    top:340px;
    left:580px;
}

#y-label{
    position:absolute;
    z-index:2;
    top:10px;
    left:220px;
}

#z-label{
    position:absolute;
    z-index:2;
    top:540px;
    left:10px;
}

#gInfo div.gText{
    position:absolute;
    z-index:-1;
    font:normal 10px Arial;
}
  1. 添加 JavaScript 辅助变量
var gData = [];
var curIndex = 0;
var trailCount = 5;
var g;
var trailingArray = [];
  1. 文档加载完成时创建图表
window.onload = function () {
    // 初始化图表
    g = new canvasGraph('graph');
    g.barStyle = {
        cap: 'rgba(255,255,255,1)',
        main: 'rgba(0,0,0,0.7)',
        shadow: 'rgba(0,0,0,1)',
        outline: 'rgba(0,0,0,0.7)',
        formatter: styleFormatter
    };
    for (i = 0; i < 100; i++) {
        gData[i] = {
            x: (Math.cos((i / 10)) * 400 + 400),
            y: (1000 - (i * 9.2)),
            z: (i * 10)
        };
    }
    plotBar();
    setInterval(plotBar, 40);
};
  1. 创建 plotBar 函数
function plotBar() {
    trailingArray.push(gData[curIndex]);
    if (trailingArray.length >= 5) trailingArray.shift();
    g.drawGraph(trailingArray);
    curIndex++;
    if (curIndex >= gData.length) curIndex = 0;
}
  1. 创建格式化函数 styleFormatter
function styleFormatter(styleColor, index, total) {
    var clrs = styleColor.split(",");
    var alpha = parseFloat(clrs[3].split(")")[0]);
    alpha *= index / total + .1;
    clrs[3] = alpha + ")";
    return clrs.join(",");
}
2.3 工作原理
  • gData 数组存储 3D 空间中的所有可能点,用于创建 3D 条形图。
  • trailingArray 数组存储当前视图中的条形元素。
  • trailCount 变量定义同时可见的条形数量。
  • curIndex 变量跟踪最新添加到图表中的元素。

当窗口加载时,创建图表元素并更新 barStyle 属性以设置条形的颜色。通过 plotBar 函数更新数据并绘制图表,每 40 毫秒重复一次。

3. 使用 flotJS 进行时间序列图表绘制

flotJS 是一个强大的图表库,具有易于更新图表信息的特点。

3.1 准备工作

可从 链接 获取最新版本的 flotJS 库。

3.2 实现步骤
  1. 创建 HTML 文件
<!DOCTYPE html>
<html>
  <head>
    <title>flot</title>
    <meta charset="utf-8" />
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script src="./external/flot/jquery.flot.js"></script>
    <script src="./external/flot/jquery.flot.fillbetween.js"></script>
    <script src="./07.03.flot.js"></script>
  </head>
  <body style="background:#fafafa">
    <div id="placeholder" style="width:600px;height:300px;"></div>
  </body>
</html>
  1. 创建 JavaScript 文件(07.03.flot.js)并创建数据源
var males = {
    // 请从源代码文件中获取完整数据
};
var VIEW_LENGTH = 5;
var index = 0;
var plot;
var formattingData = {
    xaxis: {
        tickDecimals: 0,
        tickFormatter: function (v) {
            return v % 12 + "/" + (2009 + Math.floor(v / 12));
        }
    },
    yaxis: {
        tickFormatter: function (v) {
            return v + " cm";
        }
    }
};
  1. 创建就绪事件并触发 updateChart
$(document).ready(updateChart);
  1. 创建 updateChart 函数
function updateChart() {
    plot = $.plot($("#placeholder"), getData(), formattingData);
    if (index + 5 < males['mean'].length) {
        setTimeout(updateChart, 500);
    }
}
  1. 创建 getData 函数
function getData() {
    var endIndex = index + 5 >= males.length ? males.length - 1 : index + 5;
    console.log(index, endIndex);
    var dataset = [
        {
            label: 'Male mean',
            data: males['mean'].slice(index, endIndex),
            lines: {
                show: true
            },
            color: "rgb(50,50,255)"
        },
        {
            id: 'm15%',
            data: males['15%'].slice(index, endIndex),
            lines: {
                show: true,
                lineWidth: 0,
                fill: false
            },
            color: "rgb(50,50,255)"
        },
        {
            id: 'm25%',
            data: males['25%'].slice(index, endIndex),
            lines: {
                show: true,
                lineWidth: 0,
                fill: 0.2
            },
            color: "rgb(50,50,255)",
            fillBetween: 'm15%'
        },
        {
            id: 'm50%',
            data: males['50%'].slice(index, endIndex),
            lines: {
                show: true,
                lineWidth: 0.5,
                fill: 0.4,
                shadowSize: 0
            },
            color: "rgb(50,50,255)",
            fillBetween: 'm25%'
        },
        {
            id: 'm75%',
            data: males['75%'].slice(index, endIndex),
            lines: {
                show: true,
                lineWidth: 0,
                fill: 0.4
            },
            color: "rgb(50,50,255)",
            fillBetween: 'm50%'
        },
        {
            id: 'm85%',
            data: males['85%'].slice(index, endIndex),
            lines: {
                show: true,
                lineWidth: 0,
                fill: 0.2
            },
            color: "rgb(50,50,255)",
            fillBetween: 'm75%'
        }
    ];
    index++;
    return dataset;
}
3.3 工作原理
  • VIEW_LENGTH index 用于限制可见月份的数量和跟踪当前索引。
  • formattingData 用于格式化图表的坐标轴标签。
  • getData 函数根据当前索引获取数据并更新索引。
  • updateChart 函数绘制图表并在数据可用时定时更新。
4. 使用 RaphaelJS 构建时钟

RaphaelJS 是一个强大的动画和绘图库,可用于创建创意时钟。

4.1 准备工作

可从 链接 获取原始的 RaphaelJS 库。

4.2 实现步骤
  1. 创建 HTML 文件
<!DOCTYPE html>
<html>
  <head>
    <title>Raphael</title>
    <meta charset="utf-8" />
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script src="./external/raphael/raphael-min.js"></script>
    <script src="./07.04.raphael.js"></script>
    <style>
      body {
        background: #333;
        color: #fff;
        font: 300 100.1% "Helvetica Neue", Helvetica, "Arial Unicode MS", Arial, sans-serif;
      }
      #holder {
        height: 600px;
        margin: -300px 0 0 -300px;
        width: 600px;
        left: 50%;
        position: absolute;
        top: 50%;
      }
    </style>
  </head>
  <body>
    <div id="holder"></div>
  </body>
</html>
  1. 复制路径参数到 helveticaForClock 对象
var helveticaForClock = {…};
  1. 创建 onload 监听器
window.onload = function () {
    // 后续代码将放在这里
};
  1. 创建 Raphael 对象
var r = Raphael("holder", 600, 600);
  1. 创建 arc 函数
r.customAttributes.arc = function (per, isClock) {
    var R = this.props.r,
        baseX = this.props.x,
        baseY = this.props.y;
    var degree = 360 * per;
    if (isClock) degree = 360 - degree;
    var a = (90 - degree) * Math.PI / 180,
        x = baseX + R * Math.cos(a),
        y = baseY - R * Math.sin(a),
        path;
    if (per == 1) {
        path = [["M", baseX, baseY - R], ["A", R, R, 0, 1, 1, baseX, baseY - R]];
    } else {
        path = [["M", baseX, baseY - R], ["A", R, R, 0, +(degree > 180), 1, x, y]];
    }
    var alpha = 1;
    if (per < .1 || per > .9)
        alpha = 0;
    else
        alpha = 1;
    return {
        path: path,
        stroke: 'rgba(255,255,255,' + (1 - per) + ')'
    };
};
  1. 绘制时钟的小时部分(00:00)
var transPath;
var aTrans = ['T400,100', 'T320,100', 'T195,100', 'T115,100'];
var base0 = helveticaForClock[0];
var aLast = [0, 0, 0, 0];
var aDigits = [];
var digit;
for (i = 0; i < aLast.length; i++) {
    digit = r.path("M0,0L0,0z").attr({
        fill: "#fff",
        stroke: "#fff",
        "fill-opacity": .3,
        "stroke-width": 1,
        "stroke-linecap": "round",
        translation: "100 100"
    });
    transPath = Raphael.transformPath(helveticaForClock[aLast[i]], aTrans[i]);
    digit.attr({
        path: transPath
    });
    aDigits.push(digit);
}
var dDot = r.path("M0,0L0,0z").attr({
    fill: "#fff",
    stroke: "#fff",
    "fill-opacity": .3,
    "stroke-width": 1,
    "stroke-linecap": "round",
    translation: "100 100"
});
transPath = Raphael.transformPath(helveticaForClock[':'], 'T280,90');
dDot.attr({
    path: transPath
});
  1. 创建秒针动画
var time;
var sec = r.path();
sec.props = {
    r: 30,
    x: 300,
    y: 300
};
var sec2 = r.path();
sec2.props = {
    r: 60,
    x: 300,
    y: 300
};
animateSeconds();
animateStrokeWidth(sec, 10, 60, 1000 * 60);
  1. 创建 animateSeconds 递归函数
function animateSeconds() {
    time = new Date();
    sec.attr({
        arc: [1]
    });
    sec.animate({
        arc: [0]
    }, 1000, "=", animateSeconds);
    sec2.attr({
        arc: [1, true]
    });
    sec2.animate({
        arc: [0, true]
    }, 999, "=");
    var newDigits = [
        time.getMinutes() % 10,
        parseInt(time.getMinutes() / 10),
        time.getHours() % 10,
        parseInt(time.getHours() / 10)
    ];
    var path;
    var transPath;
    for (var i = 0; i < aLast.length; i++) {
        if (aLast[i] != newDigits[i]) {
            path = aDigits[i];
            aLast[i] = newDigits[i];
            transPath = Raphael.transformPath(helveticaForClock[newDigits[i]], aTrans[i]);
            path.animate({
                path: transPath
            }, 500);
        }
    }
}
  1. 创建 animateStrokeWidth 函数
function animateStrokeWidth(that, startWidth, endWidth, time) {
    that.attr({
        'stroke-width': startWidth
    });
    that.animate({
        'stroke-width': endWidth
    }, time, function () {
        animateStrokeWidth(that, startWidth, endWidth, time); // 无限重复
    });
}
4.3 工作原理
  • arc 函数用于计算圆弧的路径。
  • animateSeconds 函数更新时钟的秒针和数字显示。
  • animateStrokeWidth 函数动画化线条的宽度。

总结

通过上述几种方法,我们可以实现不同类型的数据可视化和动画效果。模拟随机变化的仪表可以用于实时数据的动态展示;动画 3D 图表能够以直观的方式展示 3D 数据;flotJS 适合时间序列数据的可视化;而 RaphaelJS 则可用于创建创意十足的动画和绘图效果。每种方法都有其独特的优势和适用场景,开发者可以根据具体需求选择合适的工具和技术。

流程图

graph TD;
    A[开始] --> B[模拟随机仪表];
    B --> C[创建动画 3D 图表];
    C --> D[使用 flotJS 绘制时间序列图表];
    D --> E[使用 RaphaelJS 构建时钟];
    E --> F[结束];

表格

工具/技术 特点 适用场景
模拟随机仪表 随机变化仪表值,模拟驾驶速度变化 实时数据动态展示
canvas3DGraph 基于开源代码,可创建自定义 3D 图表 3D 数据可视化
flotJS 易于更新图表信息,可动态更新 x 范围 时间序列数据可视化
RaphaelJS 强大的动画和绘图库,支持路径动画 创意动画和绘图效果

5. 代码关键逻辑分析

5.1 模拟随机仪表代码分析

模拟随机仪表的代码核心在于随机改变仪表值并将其限制在一定范围内,以下是详细分析:

}else{
    meterValue += 1 - Math.random() * 2;
    meterValue = Math.max(0, Math.min(meterValue, 120)); 
}
  • 1 - Math.random() * 2 :生成一个介于 -1 到 1 之间的随机数,用于随机改变 meterValue 的值。
  • Math.max(0, Math.min(meterValue, 120)) :确保 meterValue 的值在 0 到 120 之间,避免超出范围。
5.2 动画 3D 图表代码分析

动画 3D 图表的实现涉及多个函数和变量,下面对关键部分进行分析:

5.2.1 plotBar 函数
function plotBar() {
    trailingArray.push(gData[curIndex]);
    if (trailingArray.length >= 5) trailingArray.shift();
    g.drawGraph(trailingArray);
    curIndex++;
    if (curIndex >= gData.length) curIndex = 0;
}
  • trailingArray.push(gData[curIndex]) :将当前索引对应的元素添加到 trailingArray 中。
  • if (trailingArray.length >= 5) trailingArray.shift() :当 trailingArray 的长度达到 5 时,移除第一个元素,保持数组长度不超过 5。
  • g.drawGraph(trailingArray) :绘制图表。
  • curIndex++ :当前索引加 1。
  • if (curIndex >= gData.length) curIndex = 0 :如果当前索引超过数组长度,将其重置为 0。
5.2.2 styleFormatter 函数
function styleFormatter(styleColor, index, total) {
    var clrs = styleColor.split(",");
    var alpha = parseFloat(clrs[3].split(")")[0]);
    alpha *= index / total + .1;
    clrs[3] = alpha + ")";
    return clrs.join(",");
}
  • styleColor.split(",") :将颜色字符串按逗号分割成数组。
  • parseFloat(clrs[3].split(")")[0]) :提取颜色字符串中的透明度值。
  • alpha *= index / total + .1 :根据当前索引和总长度计算新的透明度值。
  • clrs[3] = alpha + ")" :更新透明度值。
  • clrs.join(",") :将数组重新组合成颜色字符串。
5.3 flotJS 时间序列图表代码分析
5.3.1 getData 函数
function getData() {
    var endIndex = index + 5 >= males.length ? males.length - 1 : index + 5;
    console.log(index, endIndex);
    var dataset = [
        {
            label: 'Male mean',
            data: males['mean'].slice(index, endIndex),
            lines: {
                show: true
            },
            color: "rgb(50,50,255)"
        },
        // 其他数据集...
    ];
    index++;
    return dataset;
}
  • var endIndex = index + 5 >= males.length ? males.length - 1 : index + 5 :计算结束索引,确保不超出数组长度。
  • males['mean'].slice(index, endIndex) :根据当前索引和结束索引截取数据。
  • index++ :更新当前索引。
5.3.2 updateChart 函数
function updateChart() {
    plot = $.plot($("#placeholder"), getData(), formattingData);
    if (index + 5 < males['mean'].length) {
        setTimeout(updateChart, 500);
    }
}
  • $.plot($("#placeholder"), getData(), formattingData) :绘制图表。
  • if (index + 5 < males['mean'].length) { setTimeout(updateChart, 500); } :如果还有数据可用,每 500 毫秒更新一次图表。
5.4 RaphaelJS 时钟代码分析
5.4.1 arc 函数
r.customAttributes.arc = function (per, isClock) {
    var R = this.props.r,
        baseX = this.props.x,
        baseY = this.props.y;
    var degree = 360 * per;
    if (isClock) degree = 360 - degree;
    var a = (90 - degree) * Math.PI / 180,
        x = baseX + R * Math.cos(a),
        y = baseY - R * Math.sin(a),
        path;
    if (per == 1) {
        path = [["M", baseX, baseY - R], ["A", R, R, 0, 1, 1, baseX, baseY - R]];
    } else {
        path = [["M", baseX, baseY - R], ["A", R, R, 0, +(degree > 180), 1, x, y]];
    }
    var alpha = 1;
    if (per < .1 || per > .9)
        alpha = 0;
    else
        alpha = 1;
    return {
        path: path,
        stroke: 'rgba(255,255,255,' + (1 - per) + ')'
    };
};
  • degree = 360 * per :根据百分比计算角度。
  • if (isClock) degree = 360 - degree :如果是时钟模式,反转角度。
  • a = (90 - degree) * Math.PI / 180 :将角度转换为弧度。
  • x = baseX + R * Math.cos(a), y = baseY - R * Math.sin(a) :计算圆弧终点的坐标。
  • path :根据百分比生成圆弧路径。
  • alpha :根据百分比设置透明度。
5.4.2 animateSeconds 函数
function animateSeconds() {
    time = new Date();
    sec.attr({
        arc: [1]
    });
    sec.animate({
        arc: [0]
    }, 1000, "=", animateSeconds);
    sec2.attr({
        arc: [1, true]
    });
    sec2.animate({
        arc: [0, true]
    }, 999, "=");
    var newDigits = [
        time.getMinutes() % 10,
        parseInt(time.getMinutes() / 10),
        time.getHours() % 10,
        parseInt(time.getHours() / 10)
    ];
    var path;
    var transPath;
    for (var i = 0; i < aLast.length; i++) {
        if (aLast[i] != newDigits[i]) {
            path = aDigits[i];
            aLast[i] = newDigits[i];
            transPath = Raphael.transformPath(helveticaForClock[newDigits[i]], aTrans[i]);
            path.animate({
                path: transPath
            }, 500);
        }
    }
}
  • sec.attr({ arc: [1] }) sec.animate({ arc: [0] }, 1000, "=", animateSeconds) :秒针动画,从满弧到空弧,动画时长 1000 毫秒,动画完成后递归调用 animateSeconds 函数。
  • newDigits :获取当前时间的分钟和小时数字。
  • if (aLast[i] != newDigits[i]) :如果数字发生变化,更新数字显示。

6. 应用场景拓展

6.1 模拟随机仪表应用场景
  • 游戏开发 :在赛车游戏中模拟车辆的速度变化,增加游戏的真实感。
  • 工业监控 :模拟工业设备的实时运行状态,如温度、压力等的随机波动。
6.2 动画 3D 图表应用场景
  • 科学研究 :展示物理、化学等领域的 3D 数据,如分子结构、天体运动等。
  • 建筑设计 :呈现建筑模型的 3D 效果,帮助设计师和客户更好地理解设计方案。
6.3 flotJS 时间序列图表应用场景
  • 金融分析 :展示股票价格、汇率等时间序列数据的变化趋势。
  • 气象预报 :呈现气温、降水等气象数据随时间的变化情况。
6.4 RaphaelJS 时钟应用场景
  • 网页装饰 :为网页添加独特的时钟效果,提升网页的美观度。
  • 活动倒计时 :创建活动开始或结束的倒计时时钟。

7. 注意事项与优化建议

7.1 模拟随机仪表
  • 注意事项 :确保 meterValue 的初始值在合理范围内,避免出现异常情况。
  • 优化建议 :可以根据实际需求调整随机数的范围和更新频率。
7.2 动画 3D 图表
  • 注意事项 :在处理大量数据时,可能会导致性能下降,需要进行优化。
  • 优化建议 :使用数据采样技术减少数据量,或者采用 WebGL 等更高效的绘图技术。
7.3 flotJS 时间序列图表
  • 注意事项 :确保数据的准确性和完整性,避免出现数据缺失或错误。
  • 优化建议 :使用数据缓存技术减少数据请求次数,提高图表的响应速度。
7.4 RaphaelJS 时钟
  • 注意事项 :在不同浏览器和设备上可能会出现兼容性问题。
  • 优化建议 :进行充分的兼容性测试,并使用浏览器前缀等技术解决兼容性问题。

8. 总结与展望

通过本文介绍的几种方法,我们可以实现丰富多样的数据可视化和动画效果。每种方法都有其独特的优势和适用场景,开发者可以根据具体需求选择合适的工具和技术。

未来,随着技术的不断发展,数据可视化和动画效果的实现将变得更加简单和高效。例如,WebGL 的广泛应用将使得 3D 图表的性能得到进一步提升;人工智能和机器学习技术的融入将为数据可视化带来更多的创新和可能性。我们期待在未来的项目中能够看到更多精彩的数据可视化和动画效果。

流程图

graph TD;
    A[代码分析] --> B[模拟随机仪表分析];
    A --> C[动画 3D 图表分析];
    A --> D[flotJS 时间序列图表分析];
    A --> E[RaphaelJS 时钟分析];
    B --> F[应用场景拓展];
    C --> F;
    D --> F;
    E --> F;
    F --> G[注意事项与优化建议];
    G --> H[总结与展望];

表格

工具/技术 代码关键逻辑 应用场景拓展 注意事项 优化建议
模拟随机仪表 随机改变仪表值并限制范围 游戏开发、工业监控 确保初始值合理 调整随机数范围和更新频率
canvas3DGraph plotBar styleFormatter 函数 科学研究、建筑设计 处理大量数据可能性能下降 数据采样、使用 WebGL
flotJS getData updateChart 函数 金融分析、气象预报 确保数据准确完整 数据缓存
RaphaelJS arc animateSeconds 函数 网页装饰、活动倒计时 可能存在兼容性问题 兼容性测试、使用浏览器前缀
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值