Стандартный подход в динамических данных

Где либо в представлении статической информационной графики и сложных интерактивных данных нужен способ визуализации динамических данных для веба. Часто те решения которые вам представлялись были громоздкими, для примера, ручное создание нескольких структур различных данных и загрузка их вручную. Эти методы плохо масштабировались и заслуживали только снисхождения.

Традиционно такие задачи предполагалось выполнять во Flash. Убедимся что это правильный выбор. Я не буду выступать против Flash и говорить что все абсолютно для веб нужно делать с помощью HTML, CSS, и JavaScript. Flash хорош для создания опыта в динамическом веб. Однако, использование Flash в некоторых случаях подобен бытью мух кувалдой.

Использованием стандартизованных наборов веб технологий возможно создавать интересно визуализированные данные, которые ухудшают вид страницы в некотрых броузерах. Через аккуратный XHTML контейнер, умное использование CSS, и  вкрапления JavaScript, мы можем построить визуализацию данных, которая будет подчеркивать наше сообщение пользователю и лучше с ним общаться. Как основу для этой техники, я создам сайт для учебных целей.

Построение XHTML

Как и в большинстве техник,  базовый XHTML является важной частью, позволяющий нам постепенно улучшать визуальное представление. Структура нашего XHTML будет состоять из трех частей:

  1. Контейнер данных визуализации
  2. Анимированных индикатор хода процесса
  3. Текстовое представление данных

Структура для достаточно проста.


<div class="gauge">
<div class="current-value" id="campaign-progress-current">
<p>50%</p>
</div>
</div>

Может показаться что у нас есть лишний div элемент. Во многих статьях по CSS, автора стараются сделать меньшее количество div элементов необходимых для построение страницы, часто полагаясь на уже готовые блоковые элементы. Пока эта методика заслуживает внимания, я буду всегда использовать дополнительный div вместо использования p когда это нужно для визуальной компоновки ключевого элемента страницы. В дальнейшем будем использовать p тег для простого форматирования контента. Использование дополнительного div здесь защищает нашу визуальную структуру.

Формирование CSS

Компонент визуализации будет обработан посредсвом хитрой комбинации фоновых рисунков и манипуляции свойств. Когда я начал разработку эту технику инстиктивно начал использовать абсолютное позиционирование внутренних элементов, особенно заполнение контейнера внутренним элементом и сокрытие ненужных частей.

Этот метод хорошо работает с простыми блоками. Как только я начал работать с не-прямоугольными формами  it fell completely flat. The points between 0 and 100 never lined up correctly. Searching for a new solution, I struck on the idea to align the inner element with the edge of its parent and alter its width/height depending upon the value being displayed. By doing so we gradually reveal the background image of the inner element, allowing it to line up perfectly with the background element of its parent.

What we will do is create two images. The first image will represent the empty state of our visualization and the second will be the completely full state. The empty state will be applied as the background of the gauge element and the full state as the background of the progress indicator element. Technically speaking, I’m only going to create one image with both states in it.

CSS для контейнера следующий:


.gauge{
width: 77px;
height: 442px;
position:relative;
background: #333 url(gauge.gif) top left no-repeat;
}

Здесь CSS тривиален, но сделаем замечание что мы определили hex цвет для background свойства. Этот цвет увеличивает This hex color increases the technique’s accessibility and will provide a solid reference color to those who have disabled images.

Для внутреннего элемента:


.gauge .current-value {
position:absolute;
left: 0px;
bottom: 0px;
height: 50%;
text-align:center;
width: 100%;
background: red url(gauge.gif) bottom right no-repeat;
}

Because this animation is vertical I have positioned the inner element at the bottom of the container and positioned the background element at the bottom of the inner element. This technique works just fine as a horizontal animation as well, with the primary difference being where the internal element is anchored to its parent. As before, we specify a color in the event that images are disabled in the user’s browser.

From here, the only property we care about is height. In our example it is set to 50%. The entire visualization hinges on this value being accurate. You may be tempted to scrape the value from the XHTML p element with JavaScript and set the height from there, but that alienates your users with JavaScript disabled. Therefore, your options are to set this dynamically with a backend script or update it by hand. Either way is exponentially favorable to creating 100 distinct images for the intermittent states.

The last step is to hide the textual value.


.gauge .current-value * {
display:none;
}

Note: I am not here to get into a debate on the best way to hide text.

This step is optional, you may wish to leave the textual equivalent of your data displayed over the top of the visualization. I have chosen to hide it, but users who come along with CSS disabled will still be able to view the progress of the fundraiser.

At this point, you can stop. You have created a compelling visual representation that is highly reusable and degrades gracefully in the face of antiquated technology, all using skills already at your disposal. Take a look at the example page to see the results. However, if you want to add a finishing touch you can sprinkle in some JavaScript animation.

Working The JavaScript

Now that we have our gauge built, we will add an unobtrusive layer of JavaScript to animate the mercury of our gauge from zero to its current state. Those users who do not have JavaScript enabled will not see this, but those who do will be presented with a nice animation to complete the temperature gauge visualization.The first thing we will do is setup an object literal to contain our gauge animation. This will help us maintain scope and increase portability if we need to extend our JavaScript.

var gauge = {}

We are going to need to keep track of two things: the document node we’re animating and the height at which we will need to stop animating. We don’t know either of these things at the time of creation, so we’ll create variable place holders for now.


var gauge = {
targetHeight : 0,
progressMeter : new Object(),
}

My own preference when working with JavaScript object literals is to create an init() method to kick start whatever it is I will be using them for. In this case, init() will need to do three things:

  1. Verify the user’s browser supports the Document Object Model (DOM) specific methods we will be using
  2. Reset the value of the gauge to 0
  3. Animate the gauge to its final value

As such, our init() method will look like:



init : function(){
if(!document.getElementByID) return false;
this.resetValue();
this.animateGauge();
}

The first line checks to see if we can use document.getElementById and aborts if we cannot. This will exclude all browsers that do not support the standard DOM. The next two lines are for methods we have not written yet, which is something we will do now.


resetValue : function(){
this.progressMeter = document.getElementById("campaign-progress-current");
this.targetHeight = this.progressMeter.offsetHeight;
this.progressMeter.style.height = "0px";
}

This method grabs the node we wish to animate and stores it in the progressMeter variable we set above. It then grabs the current height of the node and saves it as the target height we will be animating to. Finally, it sets the height of the node to 0. Once we have done all of this, we are ready to animate the mercury to its current value.


animateGauge : function(){
var currHeight = this.progressMeter.offsetHeight;
if(currHeight == this.targetHeight){}
else{
var interval = Math.ceil((this.targetHeight - currHeight) / 10);
this.progressMeter.style.height = currHeight + interval + "px";
setTimeout("gauge.animateGauge()",30);
}
}

This function is where the magic happens. It first grabs the current height of the node and compares it to the target height and halts the animation if they are equal. If they are not equal, it runs a very simple formula to calculate an animation interval which is appended to the node’s current height. The formula we use creates a very simple illusion of easing, which is more natural to the eye than a simple linear animation. Once it’s done that, it waits 30 milliseconds and calls the animateGuage() again. This process, known as recursion, will repeat until the gauge has reached its target value. Now all we need to do is call the init() function once the window has loaded.


window.onload = function(){
gauge.init();
}

Note: I fully acknowledge that this is a very primitive onload function and should not be used in production. However, that debate is long and not appropriate to this article. Because I have used this technique you may notice some initial flickering.

Putting it all together, the final code looks like this:


window.onload = function(){
gauge.init();
}

var gauge = {

targetHeight : 0,
progressMeter : new Object(),

init : function(){
if(!document.getElementById) return false;
this.resetValue();
this.animateGauge();
},

resetValue : function(){
this.progressMeter = document.getElementById("campaign-progress-current");
this.targetHeight = this.progressMeter.offsetHeight;
this.progressMeter.style.height = "0px";
},

animateGauge : function(){
var currHeight = this.progressMeter.offsetHeight;

if(currHeight == this.targetHeight){
}

else{
var interval = Math.ceil((this.targetHeight - currHeight) / 10);
this.progressMeter.style.height = currHeight + interval + "px";
setTimeout("gauge.animateGauge()",30);
}
}
}

That’s it. Take a look at the final example to see this at work. This particular data visualization example is very simple, but it is easily extendable and limited only by your ability to create graphical representations of data. With slightly more complicated JavaScript, you can create very dynamic animations on top of this basic XHTML/CSS. As I stated in the introduction, this technique should probably not be used for large data visualization dashboards or situations requiring extensive animation. However, it is excellent at providing progressive enhancement for situations calling for low-to-moderate levels of data visualization.

: :