HTML5 + JavaScript绘柱状图

news2024/11/15 19:57:20

之前用HTML5 + JavaScript绘柱状图,可以直观显示各类型产品或品牌的所占比例大小。详见:

HTML5 + JavaScript绘柱状图+1

现在需要针对每年获得各类品牌数据进行对比,绘制柱状图会更直观。

首先我们定义二维数组aBrandType,存放品牌类型及其对应的颜色:

const aBrandType = new Array(
//0:品牌名称,1:颜色
 ["区域公用品牌",'purple'], //0
 ["农业企业品牌",'blue'], //1
 ["农产品品牌", '#ff9900'], //2
 ["广西农交会金奖", '#339933'], //3
 ["广西农交会银奖", '#cc0000'],//4
 ["中国国际农交会金奖",'#00cccc']//5
); 

然后我们定义二维数组aBrands,存放每个年度获得各类品牌的数量:

var aBrands = new Array(
 //0:年份,1:品牌总数量,2:区域公用品牌数量,3:农业企业数量;4:农产品品牌数量;5:广西农交会金奖数量……
 [2018,7,2,1,4],
 [2019,11,3,4,4],
 [2020,3,0,2,1,2],
 [2021,7,0,4,3],
 [2022,6,1,4,1]
);

接着我们与绘制饼图时定义一个饼图对象相类似的,定义一个柱状图对象BarChart

BarChart.prototype = {
……
}//BarChart.prototype


柱状图对象BarChart具有如下公开属性:

var bcBarChart = new BarChart({
    ctx:    cbcCtx,    //画布
    width:    cbc.width,//画布宽度
    height:    cbc.height,//画布高度
    data:    aBrands,//柱状数据
    legend:    aBrandType,//图例
    title:    bcBarChartTitle //标题
  });

其中bcBarChartTitle是一个对象,包括以下属性:

var  bcBarChartTitle =
{
    text:'品牌时间分布', //标题文字
    marginTop:25, //标题与画布顶部间距
    fontSize:20    //标题字体大小
};

为了简便便起见,我们默认标题颜色为黑色,字体为微软雅黑,所以这里没有标题的颜色和字体属性。

在柱状图对象BarChart内部有几个方法。

第一个是初始化方法init

    init: function ()
    {
        this.scaleX = 60;    //横坐标刻度间距
        this.scaleY = 28;    //纵坐标刻度间距 
    } //init

在初始化方法中,我们可以定义对象内部的一些属性。


第二个是绘制标题方法:

    //绘制标题
    drawTitle: function()
    {
        //如果标题属性已赋值且长度大于0,则输出标题文字
        if (typeof(this.title) != "undefined" && 0 < this.title.text.length)
        {
            this.ctx.font = "bolder " + this.title.fontSize + "px 微软雅黑";
            this.ctx.fillStyle = 'black';
            this.ctx.shadowBlur=20;
            this.ctx.shadowColor="gray";
            this.ctx.fillText(this.title.text, (this.width - this.title.text.length * this.title.fontSize)/2, this.title.marginTop);
        }//if
    },//drawTitle

第三个是绘制图例方法,包括绘制色块和对应的文字说明:

    //绘制图例
    drawLegend: function()
    {
        var maxLen = -1;
        var fontSize = 20;//字体大小
        var boxSize = 20;//色块尺寸
        var sb = 5; //间距Space between

        //取图例中文字说明字数的最大值
        this.legend.forEach(function (v) 
        {
            if (v[1] > maxLen)
            {
                maxLen = v[1].length;
            }
        });

        taDbg.value += '\n maxLen=' + maxLen;

        // 文字说明的横坐标
        //boxX = this.width - maxLen * fontSize - legendBoxSize - sb;
        var textX = this.width - maxLen * fontSize - sb*2 - 200;

        //色块的的横坐标
        var boxX = textX - boxSize - sb;

        //纵坐标
        var y = this.title.marginTop + this.title.fontSize + sb;
        this.ctx.font = fontSize + "px 宋体";

        for (var i=0; i < this.legend.length; i++)
        {
            this.ctx.fillStyle = this.legend[i][1];
            this.ctx.fillRect(boxX, y, boxSize, boxSize);
            this.ctx.fillStyle = 'black';
            this.ctx.fillText(this.legend[i][0], textX, y + fontSize*3/4);
            y += sb*2 + boxSize;
        }//for
    },//drawLegend

第四个是绘制纵坐标方法:

    //绘制纵坐标
    drawOrdinate: function()
    {
        var fontSize = 20;
        var datumPointX = 30;//坐标原点x
        var datumPointY = this.height - 50;//坐标原点y

        //1、取纵坐标最大值
        var maxOrdinate = -1;
        this.data.forEach(function (v)
        {
            if (v[1] > maxOrdinate)
            {
                maxOrdinate = v[1];
            }
        });
        taDbg.value +=     '\n maxOrdinate=' + maxOrdinate;


        //画纵坐标轴
        this.ctx.moveTo(datumPointX, datumPointY);
        this.ctx.lineTo(datumPointX,  this.title.fontSize + this.title.marginTop + fontSize);
        this.ctx.stroke();

        //画纵坐标刻度
        for (var i=0; i <= maxOrdinate; i++)
        {
            var j =  datumPointY - i * this.scaleY;
            this.ctx.moveTo(datumPointX, j);
            //this.ctx.lineTo(this.width - 50 , j);
            this.ctx.lineTo(this.width - (this.data.length-1) * this.scaleX, j);
            this.ctx.stroke();
        }

        //绘制纵坐标刻度值
        //this.ctx.textAlign = "end";
        this.ctx.font = fontSize + "px 宋体";
        for (var i=1; i <= maxOrdinate; i++)
        {
            this.ctx.fillText(i, 0, datumPointY - i * this.scaleY + fontSize/3);
        }

        //输出纵坐标名称
        //this.ctx.fillText('数量', 0, datumPointY -  maxOrdinate * this.scaleY - fontSize);
        this.ctx.fillText('数量', 0, datumPointY -  maxOrdinate * this.scaleY - fontSize*2);

        //taDbg.value +=     '\n minOrdinate=' + minOrdinate + '  maxOrdinate=' + maxOrdinate;
    },//drawOrdinate

第五个是绘制横坐标方法:

    //绘制横坐标
    drawDiascissa: function()
    {
        var fontSize = 20;

        this.ctx.font = fontSize + "px 宋体";
        for (var i=0; i < this.data.length; i++)
        {
            this.ctx.fillText(this.data[i][0], (i+1) * this.scaleX , this.height - fontSize);
        }
        //this.ctx.fillText('年度',  this.width - 4*fontSize, this.height - 50 + fontSize/3);
        this.ctx.fillText('年度',  this.width - (this.data.length-1) * this.scaleX, this.height - fontSize);    
    },//drawDiascissa

最后一个是绘制数据柱状条方法:

    //绘制数据柱状条
    drawBar: function()
    {
        var fontSize = 20;

        this.ctx.font = fontSize + "px 宋体";
        for (var i=0; i < this.data.length; i++)
        {

            var k = this.height - 50;
            for (var j=2; j < this.data[i].length; j++)
            {
                if ("undefined"!=this.data[i][j] && this.data[i][j] > 0) 
                {
                    this.ctx.fillStyle = this.legend[j-2][1];
                    k -= this.data[i][j] * 28;
                    this.ctx.fillRect((i+1) * this.scaleX, k, fontSize * 2, this.data[i][j] * this.scaleY);

                    this.ctx.fillStyle = 'white';
                    this.ctx.fillText(this.data[i][j], (i+1) * this.scaleX + Math.round(fontSize*2/3), k + this.scaleY*this.data[i][j]*2/3);
                
                    //taDbg.value += '\n this.data[' + i + '][' + j + ']=' + this.data[i][j] + '  this.ctx.fillStyle=' + this.ctx.fillStyle;
                }//if
            }//for
        }
    },//drawBar

 
完整的代码如下:

<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <meta name="Author" content="PurpleEndurer">
  <title>柱状图</title>
 </head>
 <body>
  <canvas id="canvasBarChart" width="600" height="450" style="border:1px solid black; margin:50px; overflow:auto;"> 你的浏览器不支持HTML5 canvas </canvas>
  <textarea border="1" id="taDebug" cols="80" rows="15">debug:</textarea>
    <script type="text/javascript">
var taDbg = document.getElementById("taDebug");


const aBrandType = new Array(
//0:品牌名称,1:颜色
 ["区域公用品牌",'purple'], //0
 ["农业企业品牌",'blue'], //1
 ["农产品品牌", '#ff9900'], //2
 ["广西农交会金奖", '#339933'], //3
 ["广西农交会银奖", '#cc0000'],//4
 ["中国国际农交会金奖",'#00cccc']//5
); 

var aBrands = new Array(
 //0:年份,1:品牌总数量,2:区域公用品牌数量,3:农业企业数量;4:农产品品牌数量;5:广西农交会金奖数量……
 [2018,7,2,1,4],
 [2019,11,3,4,4],
 [2020,3,0,2,1,2],
 [2021,7,0,4,3],
 [2022,6,1,4,1]
);

var cbc = document.getElementById("canvasBarChart");
cbc.height	= 450;
cbc.width	= 600;
var cbcCtx = cbc.getContext("2d");

function BarChart(obj)
{
	//导入外部公开的属性
	for(var key in obj)
	{
		this[key] = obj[key];
	}

	this.init();//初始化
	this.drawTitle();//绘制标题
	this.drawLegend();//绘制图例
	this.drawOrdinate();//绘制横坐标
	this.drawDiascissa();//绘制纵坐标
	this.drawBar();//绘制数据柱状条
}

BarChart.prototype = {
	//绘制纵坐标
	drawOrdinate: function()
	{
		//var scale = 28;//刻度
		var fontSize = 20;
		var datumPointX = 30;//坐标原点x
		var datumPointY = this.height - 50;//坐标原点y

		//1、取纵坐标最大值
		var maxOrdinate = -1;
		this.data.forEach(function (v)
		{
			if (v[1] > maxOrdinate)
			{
				maxOrdinate = v[1];
			}
		});
		taDbg.value +=	 '\n maxOrdinate=' + maxOrdinate;


		//画纵坐标轴
		
		this.ctx.moveTo(datumPointX, datumPointY);
		this.ctx.lineTo(datumPointX,  this.title.fontSize + this.title.marginTop + fontSize);
		this.ctx.stroke();

		//画纵坐标刻度
		for (var i=0; i <= maxOrdinate; i++)
		{
			var j =  datumPointY - i * this.scaleY;
			this.ctx.moveTo(datumPointX, j);
			//this.ctx.lineTo(this.width - 50 , j);
			this.ctx.lineTo(this.width - (this.data.length-1) * this.scaleX, j);
			this.ctx.stroke();
		}

		//绘制纵坐标刻度值
		//this.ctx.textAlign = "end";
		this.ctx.font = fontSize + "px 宋体";
		for (var i=1; i <= maxOrdinate; i++)
		{
			this.ctx.fillText(i, 0, datumPointY - i * this.scaleY + fontSize/3);
		}

		//输出纵坐标名称
		//this.ctx.fillText('数量', 0, datumPointY -  maxOrdinate * this.scaleY - fontSize);
		this.ctx.fillText('数量', 0, datumPointY -  maxOrdinate * this.scaleY - fontSize*2);

		//taDbg.value +=	 '\n minOrdinate=' + minOrdinate + '  maxOrdinate=' + maxOrdinate;
	},//drawOrdinate

	//绘制横坐标
	drawDiascissa: function()
	{
		var fontSize = 20;

		this.ctx.font = fontSize + "px 宋体";
		for (var i=0; i < this.data.length; i++)
		{
			this.ctx.fillText(this.data[i][0], (i+1) * this.scaleX , this.height - fontSize);
		}
		//this.ctx.fillText('年度',  this.width - 4*fontSize, this.height - 50 + fontSize/3);
		this.ctx.fillText('年度',  this.width - (this.data.length-1) * this.scaleX, this.height - fontSize);	
	},//drawDiascissa

	//绘制数据柱状条
	drawBar: function()
	{
		var fontSize = 20;

		this.ctx.font = fontSize + "px 宋体";
		for (var i=0; i < this.data.length; i++)
		{
			var k = this.height - 50;
			for (var j=2; j < this.data[i].length; j++)
			{
				if ("undefined"!=this.data[i][j] && this.data[i][j] > 0) 
				{
					this.ctx.fillStyle = this.legend[j-2][1];
					k -= this.data[i][j] * 28;
					this.ctx.fillRect((i+1) * this.scaleX, k, fontSize * 2, this.data[i][j] * this.scaleY);
					this.ctx.fillStyle = 'white';
					this.ctx.fillText(this.data[i][j], (i+1) * this.scaleX + Math.round(fontSize*2/3), k + this.scaleY*this.data[i][j]*2/3);
				
					//taDbg.value += '\n this.data[' + i + '][' + j + ']=' + this.data[i][j] + '  this.ctx.fillStyle=' + this.ctx.fillStyle;
				}//if
			}//for
		}
	},//drawBar
	
	//绘制图例
	drawLegend: function()
	{
		var maxLen = -1;
		var fontSize = 20;//字体大小
		var boxSize = 20;//色块尺寸
		var sb = 5; //间距Space between

		//取图例中文字说明字数的最大值
		this.legend.forEach(function (v) 
		{
			if (v[1] > maxLen)
			{
				maxLen = v[1].length;
			}
		});

		taDbg.value += '\n maxLen=' + maxLen;

		// 文字说明的横坐标
		//boxX = this.width - maxLen * fontSize - legendBoxSize - sb;
		var textX = this.width - maxLen * fontSize - sb*2 - 200;

		//色块的的横坐标
		var boxX = textX - boxSize - sb;

		//纵坐标
		var y = this.title.marginTop + this.title.fontSize + sb;
		this.ctx.font = fontSize + "px 宋体";

		for (var i=0; i < this.legend.length; i++)
		{
			this.ctx.fillStyle = this.legend[i][1];
			this.ctx.fillRect(boxX, y, boxSize, boxSize);
			this.ctx.fillStyle = 'black';
			this.ctx.fillText(this.legend[i][0], textX, y + fontSize*3/4);
			y += sb*2 + boxSize;
		}//for
	},//drawLegend

	//绘制标题
	drawTitle: function()
	{

		//如果标题属性已赋值且长度大于0,则输出标题文字
		if (typeof(this.title) != "undefined" && 0 < this.title.text.length)
		{
			this.ctx.font = "bolder " + this.title.fontSize + "px 微软雅黑";
			this.ctx.fillStyle = 'black';//默认黑色
			this.ctx.shadowBlur=20;
			this.ctx.shadowColor="gray";
			this.ctx.fillText(this.title.text, (this.width - this.title.text.length * this.title.fontSize)/2, this.title.marginTop);
		}//if
	},//drawTitle

	//初始化
	init: function ()
	{
		this.scaleX = 60;	//横坐标刻度间距
		this.scaleY = 28;	//纵坐标刻度间距 
	} //init
}//BarChart.prototype

var  bcBarChartTitle =
{
	text:'品牌时间分布', //标题文字
	marginTop:25, //标题与画布顶部间距
	fontSize:20	//标题字体
};

var bcBarChart = new BarChart({
	ctx:	cbcCtx,
	width:	cbc.width,
	height:	cbc.height,
	data:	aBrands,
	legend:	aBrandType,
	title:	bcBarChartTitle
  });
    </script>
 </body>
</html>

代码显示效果如下图:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/506136.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

双指针的基本应用

一、环形链表 I 方法1:哈希表 struct hashTable {struct ListNode* key;UT_hash_handle hh; };struct hashTable* hashtable;struct hashTable* find(struct ListNode* ikey) {struct hashTable* tmp;HASH_FIND_PTR(hashtable, &ikey, tmp);return tmp; }void insert(struc…

页面一打开就有30个重复请求,优化方法

一、写在前面 上周测试同事给我提了个bug。他说在公司运营系统某个编辑页面中&#xff0c;一个post请求调用太多次了&#xff0c;想让我看看怎么回事。我刚听他讲这个事情时心里有点不屑一顾&#xff0c;觉得能有多少次啊&#xff0c;大惊小怪的。然而当我在测试环境中打开那个…

经典文献阅读之--A Lifelong Learning Approach to Mobile Robot Navigation(终生学习轨迹导航)

0. 简介 终生学习作为近年来比较火的一种深度学习方式&#xff0c;导航终身学习(LLfN)旨在解决标准导航问题的一种新变体&#xff0c;在该问题中&#xff0c;智能体在有限的内存预算下&#xff0c;通过学习提高在线经验或跨环境的导航性能。而最近有一篇文章《A Lifelong Lear…

Python数据分析实战【十四】:你知道python中有几种排序方法吗【文末源码地址】

文章目录 一、List.sort()排序案例一&#xff1a;按照列表中的元素进行排序案例二&#xff1a;按照销售额数据进行排列 二、sorted()排序案例一&#xff1a;sorted()对列表进行排序案例二&#xff1a;sorted()对字典进行排序案例三&#xff1a;sorted()对列表中的字典元素排序 …

[工具]Pytorch-lightning的使用

Pytorch-lightning的使用 Pytorch-lightning介绍Pytorch-lightning与Pytorch的区别Pytorch-lightning框架的优势Pytorch-lightning框架 重要资源 Pytorch-lightning介绍 这里介绍Pytorch_lighting框架. Pytorch-lightning与Pytorch的区别 Pytorch-lightning可以简单的看作是…

shiro反序列化[cve_2016_4437]

目录 什么是shiro&#xff1f; 漏洞原理 漏洞复现 修复方案 什么是shiro&#xff1f; Apache Shiro是一款开源安全框架&#xff0c;提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用&#xff0c;同时也能提供健壮的安全性。 漏洞原理 Apache Shiro 1.2.4及以前版本…

D1. LuoTianyi and the Floating Islands (Easy Version)(树形dp)

Problem - D1 - Codeforces 这是问题的简化版本。唯一的区别在于在该版本中k≤min(n,3)。只有在两个版本的问题都解决后&#xff0c;才能进行黑客攻击。 琴音和漂浮的岛屿。 洛天依现在生活在一个有n个漂浮岛屿的世界里。这些漂浮岛屿由n−1个无向航线连接&#xff0c;任意两个…

【0基础学爬虫】爬虫基础之自动化工具 Pyppeteer 的使用

大数据时代&#xff0c;各行各业对数据采集的需求日益增多&#xff0c;网络爬虫的运用也更为广泛&#xff0c;越来越多的人开始学习网络爬虫这项技术&#xff0c;K哥爬虫此前已经推出不少爬虫进阶、逆向相关文章&#xff0c;为实现从易到难全方位覆盖&#xff0c;特设【0基础学…

Python学习之Image模块图片滤镜效果操作示例

前言 滤镜效果是图像处理中常用的一种技术&#xff0c;可以用来增强图像的视觉效果&#xff0c;实现不同的效果&#xff0c;比如增强对比度、饱和度、色彩等。滤镜效果可以帮助用户快速地调整图像的特性&#xff0c;从而使图像更加适合用户的需求。 Image模块对于图像处理的…

Hive SQL 中 map, reduce 的数据血缘分析

Hive SQL 中的有些 SQL 语句和传统关系型数据库中使用的 SQL 语句在语法和功能上都有非常大的差异。在数据血缘分析中对这些 Hive SQL 特有的 SQL 语法的支持&#xff0c;是马哈鱼数据血缘关系分析工具和一般数据血缘分析工具的一个重要区别&#xff0c;对这些特殊 SQL 语法的支…

python代码大全

Python是一种高级编程语言&#xff0c;属于通用编程语言。它是由荷兰人Guido van Rossum在1989年创造的&#xff0c;其语法简单、易读易写&#xff0c;是一种解释型、面向对象、动态数据类型的编程语言&#xff0c;支持多种编程范式&#xff0c;如面向对象、函数式、过程化等。…

FreeRTOS内核:详解Queue队列 FIFO(GPT4帮写)

FreeRTOS内核&#xff1a;详解队列管理FIFO 1. 背景2. Queue相关API2.1 xQueueCreate()&#xff1a;创建2.2 xQueueSend()&#xff1a;发送2.3 xQueueReceive()&#xff1a;接收2.4 vQueueDelete()&#xff1a;删除2.5 xQueuePeek() &#xff1a;不删除的方式从FIFO读数据&…

小程序技术给统一门户的建设带来新思路

统一门户的发展可以追溯到20世纪90年代初期&#xff0c;当时的企业和组织开始意识到信息技术可以用于整合和管理各种分散的应用程序和服务。随着互联网的普及和Web 2.0技术的兴起&#xff0c;统一门户的发展迅速加速。 在早期&#xff0c;统一门户主要采用定制化开发的方式实现…

Qt音视频开发45-音视频类结构体参数的设计

一、前言 视频监控内核组件重构和完善花了一年多时间&#xff0c;整个组件个人认为设计的最好的部分就是各种结构体参数的设计&#xff0c;而且分门别类&#xff0c;有枚举值&#xff0c;也有窗体相关的结构体参数&#xff0c;解码相关的结构体参数&#xff0c;同时将部分常用…

动态规划的学习

文章目录 动态规划的学习一、什么是动态规划&#xff1f;二、如何思考状态转移方程&#xff1f;三、动态规划的基本原理1.[509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/)1.1 暴力递归解法&#xff1a;1.1.1 递归算法的时间复杂度那为什么时间复杂度会这么…

vcruntime140.dll丢失的解决方法分享,多种修复方式

随着最新版本的Windows 10操作系统推出&#xff0c;个人电脑的性能和功能得到了巨大提升。然而&#xff0c;尽管Microsoft已经不断更新固件和驱动程序&#xff0c;但仍然存在一些常见问题&#xff0c;例如“ vcruntime140.dll丢失 ”错误。这个错误可能会导致某些应用程序无法正…

三流面试聊技术,二流面试聊框架,一流面试…

前言 本文是为了帮大家快速回顾了软件测试中知识点&#xff0c;这套面试手册涵盖了诸多软件测试技术栈的面试题和答案&#xff0c;相信可以帮助大家在最短的时间内用作面试复习&#xff0c;能达到事半功倍效果。 本来想将文件上传到github上&#xff0c;但由于文件太大有的都…

模板学堂|BI大屏制作十大高频问题的解决技巧

DataEase开源数据可视化分析平台于2022年6月正式发布模板市场&#xff08;https://dataease.io/templates/&#xff09;。模板市场旨在为DataEase用户提供专业、美观、拿来即用的仪表板模板&#xff0c;方便用户根据自身的业务需求和使用场景选择对应的仪表板模板&#xff0c;并…

4.3 线性表之链表

目录 链表的存储结构 链表的结构 链表的相关运算 链表的存储结构 将线性表L(a0,a1,……,an-1)中各元素分布在存储器的不同存储块&#xff0c;称为结点&#xff0c;通过地址或指针建立元素之间的联系 结点的data域存放数据元素ai&#xff0c;而next域是一个指针&#xff…

Android Framework开发的前景如何?

Android Framework是Android操作系统中的重要组成部分&#xff0c;它提供了一系列的API&#xff08;应用程序接口&#xff09;和服务&#xff0c;方便开发人员创建Android应用程序。随着Android设备的普及和移动互联网市场的快速发展&#xff0c;Android Framework开发有着广泛…