使用 highchart 绘制柱状图的通用方法与接口


       本文给出使用 highchart 绘制柱状图的通用方法与接口, 只要指定相应的数据结构和配置, 就可以直接拿来使用。


       一、  数据结构与基本接口  

       一般绘制图形, 会涉及到较复杂的数据结构, 比如使用 jsPlumb 绘制拓扑图的通用接口 。方法是, 首先要弄清楚绘制图形所需要的数据结构,然后根据API文档设计一个公共接口, 并写好详细的文档,避免日后忘记。先从最基本的接口开始, 见下面代码。 这是根据静态示例, 将需要动态生成或配置数据的地方抽取出来做成的接口。      

/**
 * 创建柱状图(基本接口)
 * @param chartDivName  用来绘制柱状图的 DIV-ID 值
 * @param chartData     柱状图数据结构
 *                      categories: ['c1', 'c2', ..., 'Cn']
 *                      series: [
 *                          { name: 'var1', data: [d11, d12, ..., d1n]},  
 *                          { name: 'var2', data: [d21, d22, ..., d2n]},
 *                          ...,
 *                          { name: 'varN', data: [dn1, dn2, ..., dnn]}
 *                      ]
 * @param chartConfig  柱状图全局配置
 *                        title:  图表标题
 * @returns
 */
function generateColumnChart(chartDivName, chartData, chartConfig) {
	var displayFormatter = function() {  // 当鼠标悬停图表上时, 格式化提示信息
    	var tipText = '<b>' + this.x + '</b>';
    	var total = 0;
    	$.each(this.points, function(i, point) {
        	total += point.y;
        });
        $.each(this.points, function(i, point) {
        	tipText += '<br/>'+ point.series.name +': '+ Highcharts.numberFormat((point.y*100 / total), 2) + '%' + '(' + point.y + ')';
        });
        return tipText;
    };
    
    var chartObj = obtainCommonChartObj(displayFormatter);
	chartObj.title.text = chartConfig.title;
	chartObj.xAxis.categories = chartData.categories;
	chartObj.series = chartData.series;
	var seriesNum = (chartData.series == null ? 0 : chartData.series.length);
	for (var k=0; k < seriesNum; k++) {
		chartObj.series[k].type = 'column';
	}
	
	var chartdiv = $('#'+chartDivName);
	chartdiv.highcharts(chartObj);
}


function obtainCommonChartObj(displayFormatterFunc) {
	
	   var commonChartObj = {
			chart: {
				zoomType: 'x',
				events: {
					click: null
				},
				resetZoomButton: {
					position: {
	                    x: -10,
	                    y: 10
	                },
	                relativeTo: 'chart'
				}
			},
			
			// 去掉 highcharts.com 链接
			credits: {
				enabled: false,
				text: ''
			},
			
			plotOptions: {
				series: {
					// 去掉点的marker, 使图形更美观
	                marker: {
	                    enabled: false,
	                    states: {
	                        hover: {
	                            enabled: true
	                        }
	                    }
	                },
	                turboThreshold: 0,
	                events: {
	                	click: null
	                }
	            },
	            line: {
	            	lineWidth: 1.5
	            }
			},
			
			series: [],
			xAxis: {
			},
			yAxis: {
				title: {
					text: ''
				},
			    min: 0
			},
			
			tooltip: {
				crosshairs: true,
				shared: true,
	            formatter: displayFormatterFunc
	        },
	        
		    title: {
		    	// 动态显示图表标题
		    	text: '',
		    	align: 'center',
		    	style: {
		    		fontSize: '12px',
	                margin: '3px'
		    	}
		    }
		};
	   return commonChartObj ;
}
    

       二、 对象数组与结构转化

       通常, 从服务端后台返回的数据结构是对象数组, 要使用基本接口来绘制, 就需要进行数据结构转化。 因此, 在基本接口之上, 可以构建一个高层接口, 见如下代码所示。 如果要转化的数据结构比较复杂, 那么, 可以拿一个静态的输入/输出数据示例作为范本来辅佐思考, 先通过硬编码来实现目标, 然后再将硬编码用可配置项替换掉, 达到可扩展、灵活的目标。   

/**
 * 创建柱状图(针对对象数组的高层接口)
 * @param chartDivName  用来绘制柱状图的 DIV-ID 值
 * @param chartData     对象数组
 *                      categories: ['c1', 'c2', ..., 'Cn']
 *                      data:
 *                      [{'field1': 'v11', 'field2': 'v12', ..., 'fieldN': 'v1N'},
 *                       {'field1': 'v21', 'field2': 'v22', ..., 'fieldN': 'v2N'},
 *                       ..., 
 *                       {'field1': 'vN1', 'field2': 'vN2', ..., 'fieldN': 'vNN'}]
 * @param chartConfig   柱状图全局配置
 *                       title: 图表标题
 *                       categoryField: 分类字段
 *                       groupField: 用于创建 legend 的分组字段
 *                       valueField: 用于显示 Y 轴的字段
 * @returns
 */
function generateColumnChartHighLevel(chartDivName, chartData, chartConfig) {
    
	var groupField = chartConfig.groupField;
	var valueField = chartConfig.valueField;
	var categoryField = chartConfig.categoryField;
	var categories = chartData.categories;
	
    var groupedChartData = groupByField(chartData.data, groupField);
    
    var series = [];
    
	for (var i=0; i< groupedChartData.length; i++) {
		var groupName = groupedChartData[i][groupField];
		var groupData = groupedChartData[i]['data'];
		
		var fieldData = [];
		for (var k=0; k < groupData.length; k++) {
			// 每个分类的值必须与相应的分类对应, 应对这样的情况
			// 对于每个 groupField, 并不是所有 categories 都有值, 可以通过测试例子看出来
			// 苹果在 Q3 对应的值是缺失的, 香蕉在 Q2 对应的值是缺失的 
			var categoryPosition =  getCategoryPosition(categories, groupData[k][categoryField]);
			if (categoryPosition != -1) {
				fieldData[categoryPosition] = groupData[k][valueField];
			}
		}
		for (var j=0; j < categories.length; j++) {
			// 缺失值填充
			if (fieldData[j] == null) {
				fieldData[j] = 0;
			}
		}
		var subseries = {
    		name: groupName,
    		data: fieldData,
    	};
		series.push(subseries);
	}
	
	var data = {};
    data.categories = categories;
    data.series = series;
	generateColumnChart(chartDivName, data, chartConfig);
	
}


/**
 * 检测 value 在 categories 中的位置
 * @param categories
 * @param value
 */
function getCategoryPosition(categories, value) {
	for (var index=0; index < categories.length; index++) {
		if (categories[index] == value) {
			return index;
		}
	}
	return -1;
}


/**
 * 将指定 chartData 数据按照指定字段的值分类
 * eg. [{'timestamp': 'time0', cpu: '0', sys: '15', usr: '20'}, {'timestamp': 'time0', cpu: '1', sys: '16', usr: '21'},
 *      {'timestamp': 'time1', cpu: '0', sys: '20', usr: '13'}, {'timestamp': 'time1', cpu: '1', sys: '18', usr: '10'}]
 * 转换为 [{ cpu:'0', data: [{'timestamp': 'time0', cpu: '0', sys: '15', usr: '20'}, {'timestamp': 'time1', cpu: '0', sys: '20', usr: '13'}] } ,
 *       { cpu:'1', data: [{'timestamp': 'time0', cpu: '1', sys: '16', usr: '21'}, {'timestamp': 'time1', cpu: '1', sys: '18', usr: '10'}] }] 
 */
var groupByField = function(chartDataGathered, fieldName) {
	var fieldDataMappingArray = [];
	var fieldData = {};
	var i=0, num = (chartDataGathered == null ? 0 : chartDataGathered.length);
	for (i=0; i<num; i++) {
		var fieldValue = chartDataGathered[i][fieldName];
		fieldData = obtainFieldData(fieldDataMappingArray, fieldName, fieldValue);
		if (fieldData == null) {
			fieldData = {};
			fieldData[fieldName] = fieldValue;
			fieldData['data'] = [];
			fieldDataMappingArray.push(fieldData);
		}
		fieldData['data'].push(chartDataGathered[i]);
	}
	return fieldDataMappingArray;
}


/**
 * 在 fieldDataMappingArray 中检测是否有 fieldName = fieldValue 的对象, 若有则返回; 若没有则返回 null
 * @param fieldDataMappingArray [{'fieldName': 'fieldValue1', 'data':[]}, {'fieldName': 'fieldValue2', data: []}]
 * @param fieldName the name of field
 * @param fieldValue the value of field
 */
var obtainFieldData = function(fieldDataMappingArray, fieldName, fieldValue) {
	var k=0, dataArrayLength = (fieldDataMappingArray == null ? 0 : fieldDataMappingArray.length);
	var fieldData = {};
	var existFieldData = {};
	if (dataArrayLength == 0) {
		return null;
	}
	
	for (k=0; k<dataArrayLength; k++) {
		existFieldData = fieldDataMappingArray[k];
		if (existFieldData[fieldName] == fieldValue) {
			return existFieldData;
		}
	}
	return null;
}

        三、 使用接口     

<!doctype html public "-//w3c//dtd html 4.01//en" "http://www.w3.org/tr/html4/strict.dtd">
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
		<title>highcharts 绘图示例</title>
        <script src="jquery-1.10.1.min.js"></script>
        <script src="highcharts.js"></script>
		<script src="draw_highcharts.js"></script>

        <script type="text/javascript">
		    
			$(document).ready(function() {

				 /**
				   * 一个用于测试基本接口的示例
				   * @param chartDivId
				   */
				function testGenerateColumnChart(chartDivId) {
					var categories = ['2013-11', '2013-12', '2014-01'];
					var series = [
						 { name: '苹果', data: [1500, 1300, 1200] },
						 { name: '桔子', data: [3500, 5000, 2500] },
						 { name: '香蕉', data: [2000, 1800, 1600] }
					];
					var data = {
						categories: categories, series: series	
					};
					var chartConfig = { title: '第四季度水果销量' };
					generateColumnChart(chartDivId, data, chartConfig);
			     }
			     testGenerateColumnChart('testBasicColumnchartdiv');		

				/**
				 * 测试绘制柱状图高层接口的测试例子
				 * @param chartDivId
				 */
				function testGenerateColChartHighLevel(chartDivId) {
					var data = [
						{ time: 'Q1' , fruit: '苹果', sale: 1500 },             
						{ time: 'Q1' , fruit: '桔子', sale: 1300 },
						{ time: 'Q1' , fruit: '香蕉', sale: 1400 },
						{ time: 'Q2' , fruit: '苹果', sale: 1500 }, 
						{ time: 'Q2' , fruit: '桔子', sale: 1900 },
						{ time: 'Q3' , fruit: '桔子', sale: 1700 },
						{ time: 'Q3' , fruit: '香蕉', sale: 1800 }
					];
					
					var categories = ['Q1', 'Q2', 'Q3'];
					
					var chartData = {
						categories: categories,
						data: data
					};
					
					var chartConfig = {
						title: '季度水果销量',
						categoryField: 'time',
						groupField: 'fruit',
						valueField: 'sale'
					}
					generateColumnChartHighLevel(chartDivId, chartData, chartConfig);
                }
                testGenerateColChartHighLevel('testAdvancedColumnchartdiv');
                
			});
		
		
		</script>

		<style>
			body {
			    font-family: '微软雅黑', '宋体', 'san-serif';
			}

			.chartdiv {
			    width: 90%;
                height: 250px;
			}
		</style>

	</head>

	<body>

		<div id="testBasicColumnchartdiv" class="chartdiv"></div>
        <div id="testAdvancedColumnchartdiv" class="chartdiv"></div>
	</body>
</html>
        

        四、 效果图

         使用 highchart 绘制柱状图的通用方法与接口_第1张图片

        五、 小结

        要绘制柱状图, 需要对数据结构和算法有较好的掌握, 能够自由地在各种数据结构中进行转换。通过此例, 是想再次说明了结构与算法在实际开发工作中的应用。 当然, 文中给出的代码并非是最优的, 作为一个基本的解法, 里面还是有很多可改进之处。 

 

你可能感兴趣的:(方法,接口,highchart,柱状图)