ホームページ > 記事 > ウェブフロントエンド > Canvas の楽しみを続ける: 棒グラフ プラグインの構築、パート 2
この 2 部構成のシリーズでは、汎用性の高いキャンバス要素と強力な jQuery ライブラリを組み合わせて、棒グラフ プラグインを作成します。パート 2 では、これを jQuery プラグインに変換し、目の保養やその他の機能を追加します。
終了Canvasの楽しみ2部構成のシリーズ、今日は棒グラフのプラグインを作成します;これは普通のプラグインではないことに注意してください。 jQuery が Canvas 要素を愛し、非常に強力なプラグインを作成する方法を示します。
最初の部分では、プラグインのロジックをスタンドアロン スクリプトとして実装することだけに焦点を当てました。最初の部分の終わりには、棒グラフは次のようになります。
第 1 部終了時の結果
この最後のパートでは、コードを変換して適切な jQuery プラグインにし、視覚的な詳細を追加し、最後にいくつかの追加機能を含めることに取り組みます。最終的な出力は次のようになります:
#########完成品 みなさんは暖まりましたか?はじめましょう!
プラグイン手順
プラグインの名前付け
リーリー 設定
プラグインに渡されるすべてのオプションのパラメーターが含まれます。$記号の問題の解決策
基本的に、すべてのコードを関数でラップし、それに jQuery を渡します。これで、他の JavaScript ライブラリと競合する可能性を心配することなく、コード内で $ エイリアスを自由に使用できるようになりました。
デフォルト値プラグインを設計するときは、適切な数の設定をユーザーに公開すると同時に、ユーザーがオプションを渡さずにプラグインを使用する場合は適切なデフォルト オプションも使用することが最善です。これを念頭に置いて、このシリーズの前の記事で説明した各グラフィックス オプション変数をユーザーが変更できるようにします。これを行うのは簡単で、各変数をオブジェクトのプロパティとして定義し、それらにアクセスするだけです。
必要に応じて変数名を変更してください。のように - ### リーリー ###...への変更:### リーリー
復興
これでプラグインが完成します。以前の実装ではページ内に 1 つのグラフィックしか生成できませんでしたが、ページ内に複数のグラフィックを作成できることが、この機能用のプラグインを作成した主な理由でした。さらに、ユーザーが作成するグラフごとにキャンバス要素を作成する必要がないことを確認する必要があります。これを念頭に置いて、必要に応じてキャンバス要素を動的に作成します。続けていきましょう。コードの関連部分の以前のバージョンとより新しいバージョンを見ていきます。
プラグインの呼び出し
それはとても簡単です。
yearsデータソースの取得
まず、グラフのデータ ソースを参照する必要があります。次に、ソース要素にアクセスして、その ID を取得します。前に宣言したグラフィックス変数のセットに次の行を追加します。 リーリー 新しい変数を定義し、渡された要素の ID 属性の値をそれに割り当てます。このコードでは、
this前の実装では、データ ソースの ID はハードコーディングされていました。次に、これを前に抽出した ID 属性に置き換えます。
grabValues 関数の初期バージョンは次のとおりです: リーリー 更新日:
function grabValues () { // Access the required table cell, extract and add its value to the values array. $("#"+dataSource+" tr td:nth-child(2)").each(function(){ gValues.push($(this).text()); }); // Access the required table cell, extract and add its value to the xLabels array. $("#"+dataSource+" tr td:nth-child(1)").each(function(){ xLabels.push($(this).text()); }); }
function initCanvas () { $("#"+dataSource).after("<canvas id=\"bargraph-"+dataSource+"\" class=\"barGraph\"> </canvas>"); // Try to access the canvas element cv = $("#bargraph-"+dataSource).get(0); if (!cv.getContext) { return; } // Try to get a 2D context for the canvas and throw an error if unable to ctx = cv.getContext('2d'); if (!ctx) { return; } }
我们创建一个canvas元素并将其注入到表格之后的DOM中,该表格充当数据源。 jQuery 的 after 函数在这里非常方便。还应用了 barGraph 的类属性和 barGraph-dataSourceID 格式的 ID 属性,以使用户能够根据需要将它们全部设置为组或单独设置样式。 p>
有两种方法可以实际调用此插件。您可以单独创建每个图表并仅传入一个数据源,也可以传入多个数据源。在后一种情况下,我们当前的构造将遇到错误并退出。为了纠正这个问题,我们使用 each 构造来迭代传递的元素集。
(function($){ $.fn.barGraph = function(settings) { // Option variables var defaults = { // options here }; // Merge the passed parameters with the defaults var option = $.extend(defaults, settings); // Cycle through each passed object this.each(function() { // Implementation code here }); // Returns the jQuery object to allow for chainability. return this; } })(jQuery);
我们在获取并合并 this.each 构造中的设置后封装了所有代码。我们还确保最后返回 jQuery 对象以实现可链接性。
至此,我们的重构就完成了。我们应该能够调用我们的插件并根据需要创建尽可能多的图表。
现在我们的转换已经完成,我们可以努力使其视觉效果更好。我们将在这里做很多事情。我们将分别研究它们。
旧版本使用温和的灰色来绘制图表。我们现在将为酒吧实施主题机制。这本身由一系列步骤组成。
海洋:默认主题
树叶
樱花
频谱
var defaults = { // Other defaults here theme: "Ocean", };
我们在默认设置中添加了一个主题选项,使用户能够将主题更改为四个可用预设中的任何一个。
function grabValues () { // Previous code switch(option.theme) { case 'Ocean': gTheme = thBlue; break; case 'Foliage': gTheme = thGreen; break; case 'Cherry Blossom': gTheme = thPink; break; case 'Spectrum': gTheme = thAssorted; break; } }
一个简单的switch构造查看option.theme设置并将gTheme变量指向必要的颜色数组。我们对主题使用描述性名称,而不是通用名称。
// Themes var thPink = ['#FFCCCC','#FFCCCC','#FFC0C0','#FFB5B5','#FFADAD','#FFA4A4','#FF9A9A','#FF8989','#FF6D6D']; var thBlue = ['#ACE0FF','#9CDAFF','#90D6FF','#86D2FF','#7FCFFF','#79CDFF','#72CAFF','#6CC8FF','#57C0FF']; var thGreen = ['#D1FFA6','#C6FF91','#C0FF86','#BCFF7D','#B6FF72','#B2FF6B','#AAFE5D','#A5FF51','#9FFF46']; var thAssorted = ['#FF93C2','#FF93F6','#E193FF','#B893FF','#93A0FF','#93D7FF','#93F6FF','#ABFF93','#FF9B93'];
然后我们定义许多数组,每个数组保存一系列特定颜色的色调。它们从较浅的色调开始并不断增加。稍后我们将循环遍历这些数组。添加主题就像添加您需要的特定颜色的数组一样简单,然后修改之前的开关以反映更改。
function getColour (param) { return Math.ceil(Math.abs(((gValues.length/2) -param))); }
这是一个很小的函数,可以让我们实现类似渐变的效果并将其应用于图表。本质上,我们计算要渲染的值数量的一半与传递的参数(即数组中当前所选项目的索引)之间的绝对差。这样,我们就能够创建平滑的渐变。由于我们只在每个颜色数组中定义了九种颜色,因此我们的图表仅限于十八个值。扩展这个数字应该是相当微不足道的。
function drawGraph () { for(index=0; index<gValues.length; index++) { ctx.save(); ctx.fillStyle = gTheme[getColour(index)]; ctx.fillRect( x(index), y(gValues[index]), width(), height(gValues[index])); ctx.restore(); } }
这是我们实际为图表设置主题的地方。我们没有为 fillStyle 属性设置静态值,而是使用 getColour 函数来检索当前所选主题数组中元素的必要索引。
接下来,我们将让用户能够控制所绘制条形的不透明度。设置过程分为两步。
不透明
值为 0.8
var defaults = { // Other defaults here barOpacity : 0.8, };
我们在默认值中添加了一个 barOpacity 选项,使用户能够将图形的不透明度更改为 0 到 1 之间的值,其中 0 是完全透明,1 是完全不透明。
function drawGraph () { for(index=0; index<gValues.length; index++) { ctx.save(); ctx.fillStyle = gTheme[getColour(index)]; ctx.globalAlpha = option.barOpacity; ctx.fillRect( x(index), y(gValues[index]), width(), height(gValues[index])); ctx.restore(); } }
globalAlpha 属性控制渲染元素的不透明度或透明度。我们将此属性的值设置为传递的值或默认值以增加一点透明度。作为合理的默认值,我们使用值 0.8 使其稍微透明。
网格对于处理图表中呈现的数据非常有用。虽然我最初想要一个合适的网格,但后来我选择了一系列与 Y 轴标签对齐的水平线,并完全抛弃了垂直线,因为它们只是妨碍了数据。解决了这个问题,让我们来实现一种渲染它的方法。
禁用网格
启用网格
使用路径和lineTo方法创建线条似乎是绘制图形最明显的解决方案,但我碰巧遇到了一个渲染错误,这使得这种方法不适合。因此,我也坚持使用 fillRect 方法来创建这些线。这是完整的函数。
function drawGrid () { for(index=0; index<option.numYlabels; index++) { ctx.fillStyle = "#AAA"; ctx.fillRect( option.xOffset, y(yLabels[index])+3, gWidth, 1); } }
这与绘制 Y 轴标签非常相似,只不过我们不是渲染标签,而是绘制一条横跨图形宽度、宽度为 1 px 的水平线。 y 函数帮助我们定位。
var defaults = { // Other defaults here disableGrid : false, };
我们在默认值中添加了一个 disableGrid 选项,使用户能够控制是否渲染网格。默认情况下,它是渲染的。
// Function calls if(!option.disableGrid) { drawGrid(); }
我们只是检查用户是否希望渲染网格并进行相应操作。
现在条形图都已着色,在较浅的背景下缺乏强调。为了纠正这个问题,我们需要 1px 的描边。有两种方法可以做到这一点。第一种也是最简单的方法是在 drawGraph 方法中添加一个 strokeRect 方法;或者,我们可以使用 lineTo 方法来快速绘制矩形。我选择了前一条路线,因为像之前一样,lineTo 方法向我抛出了一些奇怪的渲染错误。
没有抚摸
抚摸
首先,我们将其添加到defaults对象中,以便用户控制是否应用它。
var defaults = { // Other defaults here showOutline : true, };
function drawGraph () { // Previous code if (option.showOutline) { ctx.fillStyle = "#000"; ctx.strokeRect( x(index), y(gValues[index]), width(), height(gValues[index])); } // Rest of the code } }
我们检查用户是否想要渲染轮廓,如果是,我们继续。这与渲染实际条形几乎相同,只是我们使用 tripleRect 方法而不是使用 fillRect 方法。
在原始实现中,画布元素本身和条形的实际渲染空间之间没有区别。我们现在就纠正这个问题。
无底纹
有底纹
function shadeGraphArea () { ctx.fillStyle = "#F2F2F2"; ctx.fillRect(option.xOffset, 0, gWidth-option.xOffset, gHeight); }
这是一个很小的函数,可以遮蔽所需区域。我们覆盖画布元素减去两个轴标签覆盖的区域。前两个参数指向起点的x和y坐标,后两个参数指向所需的宽度和高度。从 option.offset 开始,我们消除了 Y 轴标签覆盖的区域,并通过将高度限制为 gHeight,我们消除了 X 轴标签。
现在我们的图表看起来足够漂亮了,我们可以集中精力向我们的插件添加一些新功能。我们将分别讨论每一个。
考虑这张著名的 8K 峰值图。
当最高值足够高,并且大多数值落在最大值的 10% 以内时,图表就不再有用。我们有两种方法来纠正这个问题。
我们将首先从更简单的解决方案开始。通过将各个图表的值呈现在顶部,实际上解决了问题,因为可以轻松地区分各个值。下面是它的实现方式。
var defaults = { // Other defaults here showValue: true, };
首先,我们向 defaults 对象添加一个条目,以使用户能够随意打开和关闭它。
// Function calls if(option.showValue) { drawValue(); }
我们检查用户是否希望显示该值并进行相应处理。
function drawValue () { for(index=0; index<gValues.length; index++) { ctx.save(); ctx.fillStyle= "#000"; ctx.font = "10px 'arial'"; var valAsString = gValues[index].toString(); var valX = (option.barWidth/2)-(valAsString.length*3); ctx.fillText(gValues[index], x(index)+valX, y(gValues[index])-4); ctx.restore(); } }
我们迭代gValues数组并单独渲染每个值。涉及 valAsString 和 valX 的计算只不过是帮助我们正确缩进的微小计算,因此它看起来并不不合适。
这是两个解决方案中更难的一个。在此方法中,我们不是从 0 开始 Y 轴标签,而是从更接近最小值的位置开始。我们边走边解释。请注意,在上面的示例中,后续值相对于最大值之间的差异非常微不足道,并且没有显示出其有效性。其他数据集应该更容易解析结果。
var defaults = { // Other defaults here scale: false };
由于scale函数是渲染过程中不可或缺的一部分,因此我们需要更新它以允许缩放功能。我们像这样更新它:
function scale (param) { return ((option.scale) ? Math.round(((param-minVal)/(maxVal-minVal))*gHeight) : Math.round((param/maxVal)*gHeight)); }
我知道这看起来有点复杂,但它看起来只是因为使用了三元条件运算符。本质上,我们检查 option.scale 的值,如果它为 false,则执行旧代码。如果为真,我们现在不会将值标准化为数组中最大值的函数,而是将其标准化为最大值和最小值之差的函数。这让我们想到:
我们现在需要找出最大值和最小值,而不是之前只能找出最大值。函数更新为:
function minmaxValues (arr) { maxVal=0; for(i=0; i<arr.length; i++) { if (maxVal<parseInt(arr[i])) { maxVal=parseInt(arr[i]); } } minVal=maxVal; for(i=0; i<arr.length; i++) { if (minVal>parseInt(arr[i])) { minVal=parseInt(arr[i]); } } maxVal*= 1.1; minVal = minVal - Math.round((maxVal/10)); }
我确信您可以在一个循环中完成相同的任务,而无需使用像我一样多的代码行,但当时我感觉特别没有创造力,所以请耐心等待。完成计算手续后,我们将 maxVal 变量增加 5%,将 minVal 变量增加 5%,减去 minVal 的 5% >maxVal 的 值。这是为了确保条形不会每次都接触顶部,并且每个 Y 轴标签之间的差异是均匀的。
完成所有基础工作后,我们现在继续更新 Y 轴标签渲染例程以反映缩放。
function drawYlabels() { ctx.save(); for(index=0; index<option.numYlabels; index++) { if (!option.scale) { yLabels.push(Math.round(maxVal/option.numYlabels*(index+1))); } else { var val= minVal+Math.ceil(((maxVal-minVal)/option.numYlabels)*(index+1)); yLabels.push(Math.ceil(val)); } ctx.fillStyle = option.labelColour; var valAsString = yLabels[index].toString(); var lblX = option.xOffset - (valAsString.length*7); ctx.fillText(yLabels[index], lblX, y(yLabels[index])+10); } if (!option.scale) { ctx.fillText("0", option.xOffset -7, gHeight+7); } else { var valAsString = minVal.toString(); var lblX = option.xOffset - (valAsString.length*7); ctx.fillText(minVal, lblX, gHeight+7); } ctx.restore(); }
如果你问我的话,更新内容相当丰富!功能的核心保持不变。我们只是检查用户是否启用了扩展并根据需要分支代码。如果启用,我们会更改 Y 标签的分配方式,以确保它们遵循新算法。现在,我们不再将最大值划分为 n 个均匀间隔的数字,而是计算最大值和最小值之间的差值,将其划分为均匀间隔的数字,并将其添加到最小值以构建 Y 轴标签数组。之后,我们照常进行,单独渲染每个标签。由于我们手动渲染了最底部的 0,因此我们必须检查是否启用了缩放,然后在其位置渲染最小值。不要介意每个传递参数的小数字添加;只是为了确保图表的每个元素都按预期排列。
在我们之前的实现中,我们对图表的维度进行了硬编码,当值的数量发生变化时,这会带来很大的困难。我们现在要纠正这个问题。
var defaults = { // Other defaults here cvHeight: 250, //In px };
我们让用户单独设置canvas元素的高度。所有其他值都是动态计算的并根据需要应用。
initCanvas 函数处理所有画布初始化,因此需要更新以实现新功能。
function initCanvas () { $("#"+dataSource).after("<canvas id=\"bargraph-"+dataSource+"\" class=\"barGraph\"> </canvas>"); // Try to access the canvas element cv = $("#bargraph-"+dataSource).get(0); cv.width=gValues.length*(option.barSpacing+option.barWidth)+option.xOffset+option.barSpacing; cv.height=option.cvHeight; gWidth=cv.width; gHeight=option.cvHeight-20; if (!cv.getContext) { return; } // Try to get a 2D context for the canvas and throw an error if unable to ctx = cv.getContext('2d'); if (!ctx) { return; } }
注入canvas元素后,我们获得了对所创建元素的引用。画布元素的宽度计算为数组中元素数量的函数 - gValues ,每个条之间的空间 - option.barSpacing ,每个条本身的宽度- option.barWidth 和最后option.xOffset。图表的宽度根据每个参数动态变化。高度是用户可修改的,默认为 220 像素,栏本身的渲染区域为 220 像素。 20px 分配给 X 轴标签。
创建图表后,用户可能希望隐藏源表,这是有道理的。考虑到这一点,我们让用户决定是否删除该表。
var defaults = { // Other defaults here hideDataSource: true, };
if (option.hideDataSource) { $("#"+dataSource).remove();}
我们检查用户是否想要隐藏表格,如果是,我们使用 jQuery 的 remove 方法将其从 DOM 中完全删除。
现在所有的艰苦工作都已经完成,我们可以回顾一下如何优化我们的代码。由于该代码完全是为了教学目的而编写的,因此大部分工作都被封装为单独的函数,而且它们比需要的要冗长得多。
如果您确实想要尽可能精简的代码,我们的整个插件(不包括初始化和计算)可以在两个循环内重写。一个循环遍历 gValues 数组来绘制条形本身和 X 轴标签;第二个循环从 0 迭代到 numYlabels 以渲染网格和 Y 轴标签。代码看起来会更加混乱,但是,它应该会导致代码库明显更小。
就是这样,伙计们!我们完全从头开始创建了一个高级插件。我们研究了本系列中的许多主题,包括:
我希望您在读这篇文章时和我在写它时一样享受乐趣。这是一个 270 多行的作品,我确信我遗漏了一些东西。欢迎点击评论并询问我。或者批评我。或者夸奖我。你知道,这是你的决定!快乐编码!
以上がCanvas の楽しみを続ける: 棒グラフ プラグインの構築、パート 2の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。