- Главная»
- Уроки»
- HTML и DHTML»
- Привлекательная анимированная круглая диаграмма
Привлекательная анимированная круглая диаграмма
В данном уроке мы разберем, как построить привлекательную круговую диаграмму, которая обладает интерактивными функциями на основе HTML5. Еще недавно такие задачи можно было решать только с использованием Flash. Но теперь, благодаря появлению элемента HTML5 canvas
мы можем создавать чудесные анимационные эффекты с использованием только JavaScript, CSS и математики!
Шаг 1. Создаем разметку
Вот разметка нашей демонстрационной страницы:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Привлекательная анимированная диаграмма | HTML5 и jQuery | Демонстрация для сайта RUSELLER.COM</title> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" > </head> <body> <div id="container"> <div class="wideBox"> <h1>Продажи штукуевин в 2010</h1> <p>Нажмите на цветном секторе в диаграмме или на строке в таблице, чтобы выдвинуть сектор!</a></p> </div> <canvas id="chart" width="600" height="500"></canvas> <table id="chartData"> <tr> <th>Штукуевина</th><th>Продажи ($)</th> </tr> <tr style="color: #0DA068"> <td>СуперШтука</td><td>1862.12</td> </tr> <tr style="color: #194E9C"> <td>МегаШтука</td><td>1316.00</td> </tr> <tr style="color: #ED9C13"> <td>ГиперШтука</td><td>712.49</td> </tr> <tr style="color: #ED5713"> <td>ЧудоШтука</td><td>3236.27</td> </tr> <tr style="color: #057249"> <td>МикроШтука</td><td>6122.06</td> </tr> <tr style="color: #5F91DC"> <td>НаноШтука</td><td>128.11</td> </tr> <tr style="color: #F88E5D"> <td>Модная Штука</td><td>245.55</td> </tr> </table> </div> </body> </html>
Разметка очень простая. Она содержит:
- div
container,
в котором размещен контент для центрирования на странице - Элемент HTML5
canvas
для круговой диаграммы - Элемент
table
, который содержит данные для диаграммы - Заголовок страницы
Отметим, что для каждого элемента tr
(строка таблицы) задан свой цвет. В коде JavaScript мы будем читать значение цвета и использовать его при рисовании соответствующего сектора диаграммы.
Шаг 2. Создаем CSS
Теперь, когда у нас есть основа HTML страницы, зададим стили CSS для различных элементов:
<style> body { background: #fff; color: #333; font-family: "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; font-size: 0.9em; padding: 40px; } .wideBox { clear: both; text-align: center; margin-bottom: 50px; padding: 10px; background: #ebedf2; border: 1px solid #333; line-height: 80%; } #container { width: 900px; margin: 0 auto; } #chart, #chartData { border: 1px solid #333; background: #ebedf2 url("images/gradient.png") repeat-x 0 0; } #chart { display: block; margin: 0 0 50px 0; float: left; cursor: pointer; } #chartData { width: 200px; margin: 0 40px 0 0; float: right; border-collapse: collapse; box-shadow: 0 0 1em rgba(0, 0, 0, 0.5); -moz-box-shadow: 0 0 1em rgba(0, 0, 0, 0.5); -webkit-box-shadow: 0 0 1em rgba(0, 0, 0, 0.5); background-position: 0 -100px; } #chartData th, #chartData td { padding: 0.5em; border: 1px dotted #666; text-align: left; } #chartData th { border-bottom: 2px solid #333; text-transform: uppercase; } #chartData td { cursor: pointer; } #chartData td.highlight { background: #e8e8e8; } #chartData tr:hover td { background: #f0f0f0; } </style>
Здесь тоже нет сюрпризов. CSS содержит правила для страницы, прямоугольников заголовка и нижнего колонтитула, контейнера, элементов #chart
canvas
и #chartData
table
.
Отметим пару моментов:
- Элементы
#chart
и#chartData
получают в качестве фона нежный градиент, создаваемый с помощью изображенияgradient.png
(оно входит в состав исходников). Да, для элементаcanvas
можно устанавливать изображения в качестве фона! - Мы используем свойство CSS3
box-shadow
(и его эквиваленты для различных браузеров), чтобы установить тень для таблицы данных. (Хотя возможно добавить тень и для элементаcanvas
, но практика показала, что в этом случае анимация диаграммы существенно замедляется в браузерах WebKit.)
Шаг 3. Включаем jQuery и библиотеку ExplorerCanvas
Теперь можно приступать к написанию JavaScript кода. сначала включим две библиотеки:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> <!--[if IE]> <script src="http://explorercanvas.googlecode.com/svn/trunk/excanvas.js"></script> <![endif]-->
jQuery не нуждается в представлении.
ExplorerCanvas. Internet Explorer, конечно же не поддерживает элемент canvas
. К счастью, несколько отличных программистов создали ExplorerCanvas, библиотеку, которая эмулирует большинство методов и свойств элемента canvas
с помощью возможностей IE SVG. Не все так хорошо, как хотелось бы, но для наших целей будет достаточно. Так как только IE нуждается в данной библиотеке, то мы используем условный комментарий для ее загрузки только в IE.
Обе библиотеки подгружаются с удаленных хранилищ, но вы можете скопировать их на локальный сервер и загружать с него.
Шаг 4. Создаем основную функцию, раздел конфигурации и полезные перменные
Мы заключим наш код в одну основную функцию pieChart()
. Таким образом, все функции и переменные, относящиеся к диаграмме, будут сконцентрированны в одном месте и не будут загрязнять глобальное пространство имен. Для вызова функции pieChart()
мы будем использовать jQuery после загрузки DOM.
Начнем с размещения переменных конфигурации в функции pieChart()
:
// Код выполняется когда DOM готова $( pieChart ); function pieChart() { // Конфигурация var chartSizePercent = 55; // Радиус диаграммы, выраженный в процентах от размеров области рисования var sliceBorderWidth = 1; // Ширина (в пискселях) границы вокруг каждого сектора var sliceBorderStyle = "#fff"; // Цвет границы вокруг каждого сектора var sliceGradientColour = "#ddd"; // Цвет, который используется с одного конца диаграммы для создания градиента var maxPullOutDistance = 25; // Насколько далеко будет выдвигаться сектор из диаграммы var pullOutFrameStep = 4; // На сколько пикселей перемещается сектор в каждом кадре анимации var pullOutFrameInterval = 40; // Сколько ms проходит между кадрами var pullOutLabelPadding = 65; // Отступ между выдвинутым сектором и его меткой var pullOutLabelFont = "bold 16px 'Trebuchet MS', Verdana, sans-serif"; // Шрифт метки выдвинутого сектора var pullOutValueFont = "bold 12px 'Trebuchet MS', Verdana, sans-serif"; // Шрифт значения выдвинутого сектора var pullOutValuePrefix = "$"; // Префикс значения выдвинутого сектора var pullOutShadowColour = "rgba( 0, 0, 0, .5 )"; // Цвет тени выдвинутого сектора var pullOutShadowOffsetX = 5; // Смещение по оси X (в пикселях) тени выдвинутого сектора var pullOutShadowOffsetY = 5; // Смещение по оси Y (в пикселях) тени выдвинутого сектора var pullOutShadowBlur = 5; // Насколько сильно размыта тень выдвинутого сектора var pullOutBorderWidth = 2; // Ширина (в пикселях) границы выдвинутого сектора var pullOutBorderStyle = "#333"; // Цвет границы выдвинутого сектора var chartStartAngle = -.5 * Math.PI; // Начало диаграммы на 12 часов, а не на 3-х // Объявдение некоторых перменных для диаграммы var canvas; // Область рисования на странице var currentPullOutSlice = -1; // Сектор, который выдвинут в текущий момент(-1 = нет выдвинутого сектора) var currentPullOutDistance = 0; // На сколько пикселей смещен текущий выдвигаемый сектор в ходе анимации var animationId = 0; // ID интервала анимации, созданный с помощью setInterval() var chartData = []; // Данные диаграммы (метки, значения, углы) var chartColours = []; // Цвета диаграммы (получены из таблицы HTML) var totalValue = 0; // Сумма всех значений в диаграмме var canvasWidth; // Ширина области рисования var canvasHeight; // Высота области рисования var centreX; // Координата X центра диаграммы на области рисования var centreY; // Координата Y центра диаграммы на области рисования var chartRadius; // Радиус диаграммы в пикселях // Инициализируем данные и рисуем диаграмму init();
Большинство из этих строк имеют очевидное значение в соответствии с комментарием. Несколько важных переменных разберем более тщательно:
chartSizePercent
- Для того, чтобы выдвигать сектор и выводить метку для него нужно иметь достаточно пространства. Реальный размер диаграммы будет меньше, чем область рисования. В нашем случае 55% является достаточной величиной.
chartStartAngle
- По умолчанию углы в JavaScript, как и в большинстве языков программирования, задаются в радианах, при этом 0 радиан соответствует положению на 3 часа.Так как мы хотим начать отсчет с 12 часов, нужно установить смещение на π/2 радиан (четверть круга) для всех углов в коде. На рисунке ниже приводится подробное объяснение.
currentPullOutSlice
иcurrentPullOutDistance
- Так как мы планируем анимировать выдвижение сектора из диаграммы, то данные переменные нужны для отслеживания анимации.
currentPullOutSlice
содержат указание на то, какой сектор выдвигается из диаграммы (величина -1 означает, что никакой сектор из диаграммы не выдвинут), аcurrentPullOutDistance
задает дистанцию, на которую выдвигается сектор. animationId
- Данная переменная содержит значение, которое возвращает функция
setInterval()
, когда мы создаем анимацию. Это числовой идентификатор, который мы можем передавать функцииclearInterval(),
когда надо завершать анимацию. chartData
- Данный массив используется для хранения данных каждого сектора в диаграмме, включая метку и значение (которые получены из таблицы HTML), начальный и конечный угол.
chartColours
- Массив, который содержит цвета для секторов. Значения также получаются из таблицы HTML.
init()
- Вызывает функцию
init()
, которая устанавливает диаграмму и запускает остальные процессы.
Шаг 5. Инициализируем диаграмму
Теперь мы готовы вывести диаграмму. Давайте разберем функцию init()
:
/** * Устанавливаем для диаграммы данные и цвета, а также устанавливаем обработчики события click * для диаграммы и таблицы. Рисуем диаграмму. */ function init() { // Получаем область рисования на странице canvas = document.getElementById('chart'); // Выходим, если браузер не имеет возможности рисовать if ( typeof canvas.getContext === 'undefined' ) return; // Инициализуем некоторые свойства области рисования и диаграммы canvasWidth = canvas.width; canvasHeight = canvas.height; centreX = canvasWidth / 2; centreY = canvasHeight / 2; chartRadius = Math.min( canvasWidth, canvasHeight ) / 2 * ( chartSizePercent / 100 ); // Получаем данные из таблицы // и устанавливаем обработчики события click для ячеек таблицы var currentRow = -1; var currentCell = 0; $('#chartData td').each( function() { currentCell++; if ( currentCell % 2 != 0 ) { currentRow++; chartData[currentRow] = []; chartData[currentRow]['label'] = $(this).text(); } else { var value = parseFloat($(this).text()); totalValue += value; value = value.toFixed(2); chartData[currentRow]['value'] = value; } // Сохраняем индекс сектора в ячейке и привязываем к ней обработчик события click $(this).data( 'slice', currentRow ); $(this).click( handleTableClick ); // Получаем и сохраняем цвет ячейки if ( rgb = $(this).css('color').match( /rgb\((\d+), (\d+), (\d+)/) ) { chartColours[currentRow] = [ rgb[1], rgb[2], rgb[3] ]; } else if ( hex = $(this).css('color').match(/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/) ) { chartColours[currentRow] = [ parseInt(hex[1],16) ,parseInt(hex[2],16), parseInt(hex[3], 16) ]; } else { alert( "Ошибка: Цвет не может быть определен! Пожалуйста, задайте таблицу цветов в формате '#xxxxxx'" ); return; } } ); // Теперь вычисляем и сохраняем начальный и конечный угол каждого сектора в диаграмме var currentPos = 0; // Текущая позиция сектора (от 0 до 1) for ( var slice in chartData ) { chartData[slice]['startAngle'] = 2 * Math.PI * currentPos; chartData[slice]['endAngle'] = 2 * Math.PI * ( currentPos + ( chartData[slice]['value'] / totalValue ) ); currentPos += chartData[slice]['value'] / totalValue; } // Все готово! Теперь выводим диаграмму и добавляем обработчик события click к ней drawChart(); $('#chart').click ( handleChartClick ); }
Заметьте, что функция init()
, так же как и все остальные функции нашего урока, должна располагаться внутри функции pieChart()
. Внутренние функции будут иметь доступ к переменным, определенным во внешней функции.
Функция init()
ужасно длинная. Вот что она делает:
- Получаем элемент
canvas
Сначала получаем элемент"#chart"
canvas
со страницы и сохраняем его в объектеcanvas
. Мы будем выполнять все рисование через данный объект. - Проверяем поддержку элемента
canvas
в браузере
Прежде, чем начать что либо делать, надо проверить факт, что браузер поддерживает элемент HTML5canvas
. Для этого мы проверяем, что объектcanvas
содержит методgetContext()
— часто используемый метод элемента. Если его нет, то браузер вероятно не поддерживает элементcanvas
, значит следует прервать выполнение функции. - Вычисляем и сохраняем размеры области рисования и диаграммы
Так как мы будем часто использовать такие значения как ширина, высота и центр области рисования, а также радиус диаграммы, то нужно их вычислить и сохранить в переменных. - Получаем данные из таблицы
Мы используем селектор jQuery$('#chartData td')
для выделения всех данных в таблице. Затем мы можем пройти циклом по всем ячейкам с помощью метода jQueryeach()
. Для каждой ячейки мы определяем ее метку (например, "СуперШтука") или значение (например, "1862.12"), в зависимости от того, в какой колонке находится величина. Затем мы сохраняем содержание ячейки под ключами'label'
или'value'
в ассоциированном массиве, который помещается в массивchartData
. - Сохраняем индекс сектора с каждой ячейкой таблицы, и назначаем обработчик события
click
ячейке
При прохождении по ячейкам таблицы мы сохраняем текущий индекс строки (соответствует индексу сектора) в ключе'slice'
в объекте jQuery, который содержит ячейку таблицы. Для этого используется метод jQuerydata()
. Таким образом мы можем легко определить, какому сектору соответствует ячейка, если на ней произойдет нажатие кнопки мыши. Мы также назначаем обработчик событияclick
функциюhandleTableClick()
ячейке, таким образом, если на ячейке будет нажата кнопка мыши, то мы можем правильно анимировать диаграмму. - Получаем цвет ячейки и сохраняем его в массиве
chartColours
Мы используем jQuery для того, чтобы получить цвет ячейки из свойства CSScolor
. Затем мы сохраняем цвет в массивеchartColors
как трехэлементный массив , который содержит значения для красного, синего и зеленого уровней (в десятичном виде).Большинство браузеров возвращают цвет элемента в формате
"rgb(r, g, b)"
. Однако, некоторые браузеры (*кхе* IE *кхе*) просто возвращает цвет в том формате, который был задан в CSS (например,"#RRGGBB"
). Таким образом, наш код использует регулярные выражения для проверки обоих сценариев. - Вычисляем и сохраняем начальный и конечный угол для каждого сектора
Нам нужно знать, с каких углов начинается и заканчивается сектор, практически во всем коде. Поэтому, предварительный расчет и хранения данных величин в элементах'startAngle'
и'endAngle'
ассоциированного массива внутри массиваchartData
является очень нужным действием. Для вычислений используется цикл по всем секторам с использованием перменнойcurrentPos
для сохранения промежуточного итога как отношения к общему итогу (между 0 и 1). Затем мы можем умножать промежуточный итог на 2π радиан (полный круг), чтобы получить начальный и конечный углы сектора.Углы сектора сохраняются в
chartData
в значении от 0 до 2π (от 3-х часов до 3-х часов) . Нужно сместить данные углы с помощью перменнойchartStartAngle
при рисовании сектора,так как точка отсчета находится на 12 часов. - Рисуем диаграмму и присоединяем обработчик события
click
к элементуcanvas
В завершении функцияinit()
вызывает фукнциюdrawChart()
для вывода диаграммы на экран. А также назначает обработчик событияclick
функциюhandleChartClick()
элементуcanvas
, так что если нажать кнопку мыши на диаграмме, то сектор либо выдвинется либо встанет на место в зависимости от условий.
Шаг 6. Пишем обработчик события click
для диаграммы
Теперь надо написать функцию обработчик события handleChartClick()
. Она вызывается автоматически, когда пользователь нажимает кнопку мыши на элементе canvas
.
Вот код функции:
/** * Обрабатываем нажатие кнопки мыши в области диаграммы. * * Если нажатие произошло на секторе, переключаем его положение (задвинут/выдвинут). * Если нажатие произошло вне области диагрммы, то задвигаем все сектора на место. * * @param Event Событие click */ function handleChartClick ( clickEvent ) { // Получаем положение курсора в момент нажатия кнопки мыши, по отношению к области рисования var mouseX = clickEvent.pageX - this.offsetLeft; var mouseY = clickEvent.pageY - this.offsetTop; // Кнопку мыши нажали внутри диаграммы? var xFromCentre = mouseX - centreX; var yFromCentre = mouseY - centreY; var distanceFromCentre = Math.sqrt( Math.pow( Math.abs( xFromCentre ), 2 ) + Math.pow( Math.abs( yFromCentre ), 2 ) ); if ( distanceFromCentre <= chartRadius ) { // Да, кнопку мыши нажали внутри длиаграммы. // Ищем сектор, в котором была нажата кнопка мыши. // Определяем угол по отношению к центру диаграммы. var clickAngle = Math.atan2( yFromCentre, xFromCentre ) - chartStartAngle; if ( clickAngle < 0 ) clickAngle = 2 * Math.PI + clickAngle; for ( var slice in chartData ) { if ( clickAngle >= chartData[slice]['startAngle'] && clickAngle <= chartData[slice]['endAngle'] ) { // Сектор найден. Выдвигаем его или задвигаем, в соответствиис текущим положением. toggleSlice ( slice ); return; } } } // Должно быть пользователь нажал кнопку мыши вне диаграммы. Нужно задвинуть все сектора на место. pushIn(); }
Как и все обработчики событий jQuery, функция handleChartClick()
должна принимать в качестве аргумента объект jQuery Event
. Объект Event
содержит информацию о событии click
, включая координаты точки, где произошло событие.
Функция выполняет следующие операции:
- Получает координаты положения курсора мыши в момент нажатия кнопки
Координаты точки, в которой находился курсор мыши в момент события доступны через свойстваpageX
иpageY
объектаEvent
. Однако, данные координаты соотнесены со страницей, то есть нам надо вычесть координаты верхнего левого угла элементаcanvas
, чтобы получить положение соотнесенное с областью рисования. Это очень просто, потому чтоthis
в обработчике событияclick
ссылается на элемент, в котором произошло событие (в нашем случае на элементcanvas
). Мы можем затем получить положение верхнего левого угла с помощьюoffsetLeft
иoffsetTop
. - Определяем, было ли сделано нажатие кнопки мыши внутри диаграммы
Теперь мы знаем в какой части области рисования произошло нажатие кнопки мыши и нужно определить, попадает ли данная точка в радиус диаграммы. С помощью теоремы Пифагора определяем дистанцию от данной точки до центра диаграммы. - Находим на какой сектор нажали
Предполагая, что нажатие произошло в диаграмме, определяем на каком секторе произошло нажатие. Для этого нужно вычислить угол положения точки нажатия кнопки мыши по отношению к оси Х диаграммы с помощью функцииatan2()
. ЗначениеchartStartAngle
вычитается из вычисленной величины, так как диаграмма провернута относительно своего центра. Так какatan2()
может вернуть отрицательное значение, нужно прибавить к нему 2π в этом случае, таким образом мы получим положительное значение в диапазоне от 0 до 2π. Теперь мы можем пройти циклом по секторам, до тех пор пока не найдем тот, чьи начальные и конечные значения углов соответствуют углу точки положения курсора во время события. Это нужный сектор. - Переключаем положение сектора
Теперь мы знаем, в каком секторе была нажата кнопка мыши, и можно вызывать функциюtoggleSlice()
, передав ей индекс сектора. Данная функция запускает процесс перемещения сектора в зависимости от его текущего положения. - Если пользователь нажал на кнопку мыши вне пределов диаграммы, то надо задвинуть все сектора на место
Если пользователь нажал кнопку мыши вне пределов диаграммы, то отличным решением будет задвинуть все сектора на место. Для этого мы вызываем функциюpushIn(),
которая задвигает все сектора обратно.
Шаг 7. Пишем обработчик события click
для таблицы
Обработчик события click
для таблицы вызывается в том случае, если пользователь нажал кнопку мыши на ячейке таблицы. В этом случае надо переключить положение соответствующего сектора:
/** * Обрабатываем событие click в области таблицы. * * Возвращает номер сектора из данных jQuery, сохраненных в * нажатой ячейке, затем переключаем сектор. * * @param Event Событие click */ function handleTableClick ( clickEvent ) { var slice = $(this).data('slice'); toggleSlice ( slice ); }
Обработчик события click
для таблицы значительно проще обработчика для диаграммы! В функции init()
мы сохраняли индекс сектора для каждой ячейки таблицы с помощью метода jQuery data().
Теперь легко определить, какой сектор нужно переключить с помощью простого вызова $(this).data('slice')
.
Шаг 8. Пишем функцию для переключения положения сектора
Данная функция вызывается из двух обработчиков события. Оно должна либо выдвинуть сектор, либо задвинуть его на место, в зависимости от текущего положения:
/** * Задвигаем/выдвигаем сектор. * * Если сектор выдвинут - задвигаем его. И наоборот. * * @param Number Индекс сектора (между 0 и количеством секторов - 1) */ function toggleSlice ( slice ) { if ( slice == currentPullOutSlice ) { pushIn(); } else { startPullOut ( slice ); } }
Функция не выполняет реальных операций по движению сектора, вместо этого она вызывает функции pushIn()
и startPullOut()
в зависимости от положения сектора.
Шаг 9. Cоздаем функцию для выдвижения сектора
Когда сектор выдвигается, мы будем анимировать движение. Для этого будет использоваться функция JavaScript setInterval()
для вывода кадра анимации каждые несколько миллисекунд.
Функция startPullOut()
запускает анимацию. Она использует setInterval()
для вызова функции анимации animatePullOut()
, а также выделяет соответствующую строку в таблице:
/** * Запускаем выдвижение сектора из диаграммы. * * @param Number Индекс сектора (между 0 и количеством секторов - 1) */ function startPullOut ( slice ) { // Выходим, если сектор уже выдвинут if ( currentPullOutSlice == slice ) return; // Записываем сектор, который надо выдвинуть. Очищаем предыдущие анимации. Запускаем анимацию. currentPullOutSlice = slice; currentPullOutDistance = 0; clearInterval( animationId ); animationId = setInterval( function() { animatePullOut( slice ); }, pullOutFrameInterval ); // Выделяем соответствующую строку в таблице $('#chartData td').removeClass('highlight'); var labelCell = $('#chartData td:eq(' + (slice*2) + ')'); var valueCell = $('#chartData td:eq(' + (slice*2+1) + ')'); labelCell.addClass('highlight'); valueCell.addClass('highlight'); }
Несколько замечаний по данной функции:
- При вызове функции
setInterval()
в нее передается анонимная функция, которая преобразуется в вызовanimatePullOut( slice )
. Это прекрасный пример замыкания, так как анонимная функция имеет доступ к переменной (slice
) в контексте окружающей функции. Таким образом обеспечивается доступ функцииsetInterval()
к функцииanimatePullOut()
и значению переменнойslice
. - Функции
setInterval()
передается интервал в миллисекундах с помощью переменнойpullOutFrameInterval
. ПоэтомуsetInterval()
будет вызыватьanimatePullOut()
каждыеpullOutFrameInterval
миллисекунд. setInterval()
возвращает ID интервала, который сохраняется в переменнойanimationId
. Мы можем затем вызватьclearInterval()
с указанием ID тогда, когда потребуется остановить анимацию.- Для выделения строки сначала мы удаляем класс
'highlight'
у все ячеек таблицы, затем мы используем селектор jQuery:eq()
для того, чтобы найти 2 ячейки в целевой строке и присваиваем им класс'highlight'
.
Шаг 10. Создаем функцию для анимации эффекта выдвижения
Теперь нам нужно написать функцию animatePullOut()
, которая анимирует каждый кадр эффекта выдвижения. Данная функция очень простая. Она производит реальное рисование следующего кадра с помощью функции drawChart()
:
/** * Рисуем кадр анимации выдвижения. * * @param Number Индекс сектора, который выдвигается */ function animatePullOut ( slice ) { // Выдвигаем сектор на шаг анимации currentPullOutDistance += pullOutFrameStep; // Если сектор выдвинут до нужного положения - заканчиваем анимацию if ( currentPullOutDistance >= maxPullOutDistance ) { clearInterval( animationId ); return; } // Выводим кадр drawChart(); }
В функции просто добавляется значение переменной pullOutFrameStep
к переменной currentPullOutDistance
для выдвижения сектора еще на несколько пикселей, а затем вызывается функция drawChart()
для вывода кадра. Также проверяется, если сектор уже выдвинут на максимальное расстояние (maxPullOutDistance
). В случае подтверждения данного анимация останавливается вызовом функции clearInterval()
.
Шаг 11. Создаем функцию для возвращения сектора на место
Функция pushIn()
вызывается функциями handleChartClick()
и toggleSlice()
тогда, когда нужно поставить любой выдвинутый сектор на место:
/** * Задвигаем выдвинутые сектора на место. * * Сбрасывает переменные анимации и перерисовывает диаграмму. * Также сбрасывает выделение строк в таблице.ы */ function pushIn() { currentPullOutSlice = -1; currentPullOutDistance = 0; clearInterval( animationId ); drawChart(); $('#chartData td').removeClass('highlight'); }
Данная функция сбрасывает значения переменных currentPullOutSlice
и currentPullOutDistance
, очищает любую анимацию с помощью вызова clearInterval()
, перерисовывает диаграмму в соответствии с новыми условиями и удаляет выделение в таблице данных.
Шаг 12. Пишем функцию рисования диаграммы
Теперь напишем функцию, которая будет реально рисовать диаграмму! Функция drawChart()
в действительности достаточно прямолинейная, так как она перекладывает часть работы на фукнцию drawSlice()
:
/** * Рисуем диаграмму. * * Проходит циклом по всем секторам и рисует их. */ function drawChart() { // Получаем контекст для рисования var context = canvas.getContext('2d'); // Очищаем область рисования context.clearRect ( 0, 0, canvasWidth, canvasHeight ); // Рисуем каждый сектор диаграммы, пропуская выдвинутый (если он есть) for ( var slice in chartData ) { if ( slice != currentPullOutSlice ) drawSlice( context, slice ); } // Если есть выдвинутый сектор, рисуем его. // (мы рисуем выдвинутый сектор последним, таким образом его тень не будет перекрываться другими секторами.) if ( currentPullOutSlice != -1 ) drawSlice( context, currentPullOutSlice ); }
Рассмотрим функцию по шагам:
- Получаем контекст для рисования.
Для того чтобы нарисовать что-нибудь в элементеcanvas
нужно сначала получить контекст рисования. Это объект, который имеет набор методов для рисования в элементеcanvas
. Для получения контекста вызываетсяcanvas.getContext()
, которому передается параметр'2d'
, индицирующий что нам нужен двумерный контекст. (3D еще не реализован) - Очищаем элемент
canvas.
Так как мы намереваемся рисовать кадры анимации, то сначала нужно удалить предыдущие кадры. Используется методclearRect()
, который дает хороший результат в большинстве браузеров. Он очищает прямоугольную область, заданную координатами верхнего левого угла, шириной и высотой. - Рисуем все, за исключением выдвигаемого сектора.
Теперь можно циклом пройтись по всем секторам в массивеchartData
и нарисовать сектора с помощью методаdrawSlice()
, за исключением выдвигаемого сектора. - Рисуем выдвигаемый сектор.
В завершении рисуем выдвигаемый сектор. Проверяем есть ли перемещаемый сектор с помощью проверки значения переменнойcurrentPullOutSlice
. если да, то вызываем функциюdrawSlice()
опять, передавая ей индекс выдвигаемого сектора.
Выдвигаемый сектор рисуется последним, потому что у него есть тень. Если рисовать все сектора в порядке следования в таблице, то сектора, которые будут выводиться после перемещаемого , могут перекрывать нарисованную тень.
Шаг 13. Cтроим функцию, которая рисует каждый сектор в диаграмме
Теперь делаем функцию, которая является сердцем скрипта. drawSlice()
получает контекст рисования и индекс сектора, который будет выводиться:
/** * Рисуем отдельный сектор в диаграмме. * * @param Context Контекст области рисования * @param Number Индекс сектора */ function drawSlice ( context, slice ) { // Вычисляем выверенные начальный и конечный углы для сектора var startAngle = chartData[slice]['startAngle'] + chartStartAngle; var endAngle = chartData[slice]['endAngle'] + chartStartAngle; if ( slice == currentPullOutSlice ) { // Сектор выдвигается (или уже выдвинут). // Смещаем его от центра диаграммы, рисуем текстовую метку, // и добавляем тень. var midAngle = (startAngle + endAngle) / 2; var actualPullOutDistance = currentPullOutDistance * easeOut( currentPullOutDistance/maxPullOutDistance, .8 ); startX = centreX + Math.cos(midAngle) * actualPullOutDistance; startY = centreY + Math.sin(midAngle) * actualPullOutDistance; context.fillStyle = 'rgb(' + chartColours[slice].join(',') + ')'; context.textAlign = "center"; context.font = pullOutLabelFont; context.fillText( chartData[slice]['label'], centreX + Math.cos(midAngle) * ( chartRadius + maxPullOutDistance + pullOutLabelPadding ), centreY + Math.sin(midAngle) * ( chartRadius + maxPullOutDistance + pullOutLabelPadding ) ); context.font = pullOutValueFont; context.fillText( pullOutValuePrefix + chartData[slice]['value'] + " (" + ( parseInt( chartData[slice]['value'] / totalValue * 100 + .5 ) ) + "%)", centreX + Math.cos(midAngle) * ( chartRadius + maxPullOutDistance + pullOutLabelPadding ), centreY + Math.sin(midAngle) * ( chartRadius + maxPullOutDistance + pullOutLabelPadding ) + 20 ); context.shadowOffsetX = pullOutShadowOffsetX; context.shadowOffsetY = pullOutShadowOffsetY; context.shadowBlur = pullOutShadowBlur; } else { // Данный сектор не выдвинут, рисуем его от центра диаграммы startX = centreX; startY = centreY; } // Устанавливаем градиент для заполнения сектора var sliceGradient = context.createLinearGradient( 0, 0, canvasWidth*.75, canvasHeight*.75 ); sliceGradient.addColorStop( 0, sliceGradientColour ); sliceGradient.addColorStop( 1, 'rgb(' + chartColours[slice].join(',') + ')' ); // Рисуем сектор context.beginPath(); context.moveTo( startX, startY ); context.arc( startX, startY, chartRadius, startAngle, endAngle, false ); context.lineTo( startX, startY ); context.closePath(); context.fillStyle = sliceGradient; context.shadowColor = ( slice == currentPullOutSlice ) ? pullOutShadowColour : "rgba( 0, 0, 0, 0 )"; context.fill(); context.shadowColor = "rgba( 0, 0, 0, 0 )"; // Задаем соответствующий стиль границы сектора if ( slice == currentPullOutSlice ) { context.lineWidth = pullOutBorderWidth; context.strokeStyle = pullOutBorderStyle; } else { context.lineWidth = sliceBorderWidth; context.strokeStyle = sliceBorderStyle; } // Рисуем границу сектора context.stroke(); }
Вот какие операции выполняет функция:
- Вычисляем выверенные углы сектора.
Помниет, что отсчет угла сектора начинается с 0 радиан (3 часа), но мы хотим начинать отсчет с 12 часов. То есть нужно вычесть четверть оборота из угла сектора перед тем, как использовать его в рисовании. Операция выполняется с помощью переменнойchartStartAngle,
результат хранится в переменныхstartAngle
иendAngle
. - Для выдвигаемого сектора устанавливается смещение от центра диаграммы.
Следующий блок кода проверяет, выводим ли мы выдвигаемый сектор. Если да, то нужно установить смещение секктора относительно центра. Для этого мы вычисляем середину сектора (половина между начальным и конечным угломe), и получаем количество пикселей, на которое надо сдвинуть сектор с помощью умножения значения перменнойcurrentPullOutDistance
на результат функцииeaseOut()
. Затем мы можем использовать простую тригонометрию (косинус и синус) для вычисления новой стартовой точки для сектора. - Для выдвигаемого сектора рисуем метку и устанавливаем тень.
После вычисления стартовой точки выдвигаемого сектора рисуем текстовую метку, которая состоит из метки из таблицы (например, "СуперШтука"), значения (например, "$1862.12") и процентного вырадения доли от общего количества.context.fillText()
выводит текст после точкис координатами X и Y. Данные координаты вычисляются с использванием синуса и косинуса, прибавлением отступа (pullOutLabelPadding
) от центра диаграммы, который предоставлет достаточно места для вывода метки. - Для обычного сектора рисование ведется от центра диаграммыы.
Если сектор не выдвигается, то стартовая точка - центр диаграммы. - Установка градиента.
Градиент добавляет немного шарма диаграмме. Мы используем методcontext.createLinearGradient()
для создания линейного градиента. Затем мы вызываем два разаaddColorStop()
чтобы добавить установить граничные цвета градиента ("#ddd"
или светло серый и цвет сектора). - Рисуем сектор.
Для рисования вызываемbeginPath(),
который открывает контур. Затем перемещаем точку вывода с помощью методаmoveTo()
и рисуем дугу с помощью методаarc()
. Данная функция рисует прямую линию от текущей точки под начальным углом длиной заданного радиуса, затем рисует дугу до точки, заданной конечным углом. Мы завершаем сегмент с помощью методаlineTo()
, которому указывает в качестве конечной точки стартовую позицию сектора. Закрываем контур с помощьюclosePath()
. Затем задаем градиент в качестве заполнения, добавляем тень для выдвигаемого сектора и вызываем методfill()
для заполнения сектора.Последний аргумент функции
arc()
—false
— сообщает функции о том, что дугу нужно рисовать против часовой стрелки.Цвет тени задан с помощью формата
rgba
, в котором последняя величина устанавливает уровень прозрачности. Таким образом"rgba( 0, 0, 0, 0 )"
выводит тень с нулевой прозрачностью, эффективно скрывая тень, если она не нужна. - Рисуем границу сектора.
В завершении рисуем тонкую светлую границу вокруг обычного сектора и более толстую темную границу вокруг выдвигаемого сектора. Ширину и цвет границы получаем из переменных, которые созданы в функцииinit().
Устанавливает значения свойствcontext.lineWidth
иcontext.strokeStyle,
а затем рисуем границу с помощью методаcontext.stroke()
.
Шаг 14. Создаем сглаживающую функцию
Последняя функция называется сглаживающей. Она вызывается функцией drawSlice()
, ее назначение - затормозить анимацию к концу ее проведения:
/** * Вспомогательная функция вычисления плавности перехода * * Выглядит странно, но работает. * * @param Number Отношение текущей пройденной дистанции к максимальному расстоянию * @param Number Степень (чем выше число, тем плавнее переход) * @return Number Новое отношение */ function easeOut( ratio, power ) { return ( Math.pow ( 1 - ratio, power ) + 1 ); } };
Код функции выглядит неким шаманством, но он работает. За счет манипулирования отношением между пройденным расстоянием и общей длиной пути, получается, что в начале сектор смещается на больший шаг, чем в конце своего перемещения.
Готово!
Данный урок подготовлен для вас командой сайта ruseller.com
Источник урока: www.elated.com/articles/snazzy-animated-pie-chart-html5-jquery/
Перевел: Сергей Фастунов
Урок создан: 30 Сентября 2010
Просмотров: 42574
Правила перепечатки
5 последних уроков рубрики "HTML и DHTML"
-
Лайфхак: наиполезнейшая функция var_export()
При написании или отладки PHP скриптов мы частенько пользуемся функциями var_dump() и print_r() для вывода предварительных данных массив и объектов. В этом посте я бы хотел рассказать вам о функции var_export(), которая может преобразовать массив в формат, пригодный для PHP кода.
-
17 бесплатных шаблонов админок
Парочка бесплатных шаблонов панелей администрирования.
-
30 сайтов для скачки бесплатных шаблонов почтовых писем
Создание шаблона для письма не такое уж простое дело. Предлагаем вам подборку из 30 сайтов, где можно бесплатно скачать подобные шаблоны на любой вкус.
-
Как осуществить задержку при нажатии клавиши с помощью jQuery?
К примеру у вас есть поле поиска, которое обрабатывается при каждом нажатии клавиши клавиатуры. Если кто-то захочет написать слово Windows, AJAX запрос будет отправлен по следующим фрагментам: W, Wi, Win, Wind, Windo, Window, Windows. Проблема?.
-
15 новых сайтов для скачивания бесплатных фото
Подборка из 15 новых сайтов, где можно скачать бесплатные фотографии для заполнения своих сайтов.