Статьи

Приложение для визуализации данных с использованием GAE Python, D3.js и Google BigQuery: часть 3

В предыдущей части этого урока мы увидели, как начать работу с D3.js , и создали динамические масштабы и оси для нашего графика визуализации, используя образец набора данных. В этой части руководства мы построим график, используя образец набора данных.

Для начала клонируйте исходный учебный код из GitHub .

1
git clone https://github.com/jay3dec/PythonD3jsMashup_Part2.git

Перейдите в каталог SDK Google App Engine (GAE) и запустите сервер.

1
./dev_appserver.py PythonD3jsMashup_Part2/

Укажите в браузере http: // localhost: 8080 / displayChart, и вы сможете увидеть оси X и Y, которые мы создали в предыдущем уроке.

Прежде чем начать, создайте новый шаблон с именем displayChart_3.html который будет таким же, как displayChart.html . Также добавьте маршрут для displayChart_3.html . Это сделано только для того, чтобы сохранить демонстрацию предыдущего урока нетронутой, поскольку я буду размещать ее по тому же URL-адресу.

01
02
03
04
05
06
07
08
09
10
11
12
13
class DisplayChart3(webapp2.RequestHandler):
  def get(self):
    template_data = {}
    template_path = ‘Templates/displayChart_3.html’
    self.response.out.write(template.render(template_path,template_data))
     
 
application = webapp2.WSGIApplication([
    (‘/chart’,ShowChartPage),
    (‘/displayChart’,DisplayChart),
    (‘/displayChart3’,DisplayChart3),
    (‘/’, ShowHome),
], debug=True)

Из нашего образца набора данных у нас есть число count для набора соответствующего year .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
var data = [{
    «count»: «202»,
    «year»: «1590»
}, {
    «count»: «215»,
    «year»: «1592»
}, {
    «count»: «179»,
    «year»: «1593»
}, {
    «count»: «199»,
    «year»: «1594»
}, {
    «count»: «134»,
    «year»: «1595»
}, {
    «count»: «176»,
    «year»: «1596»
}, {
    «count»: «172»,
    «year»: «1597»
}, {
    «count»: «161»,
    «year»: «1598»
}, {
    «count»: «199»,
    «year»: «1599»
}, {
    «count»: «181»,
    «year»: «1600»
}, {
    «count»: «157»,
    «year»: «1602»
}, {
    «count»: «179»,
    «year»: «1603»
}, {
    «count»: «150»,
    «year»: «1606»
}, {
    «count»: «187»,
    «year»: «1607»
}, {
    «count»: «133»,
    «year»: «1608»
}, {
    «count»: «190»,
    «year»: «1609»
}, {
    «count»: «175»,
    «year»: «1610»
}, {
    «count»: «91»,
    «year»: «1611»
}, {
    «count»: «150»,
    «year»: «1612»
}];

Мы представим каждую точку данных в виде кружков на нашем графике визуализации. D3.js предоставляет методы API для создания различных форм и размеров.

Сначала мы будем использовать d3.selectAll, чтобы выделить круги внутри элемента визуализации. Если элементы не найдены, он вернет пустой заполнитель, где мы можем добавить круги позже.

1
var circles = vis.selectAll(«circle»);

Далее мы свяжем наш набор данных с выбором circles .

1
var circles = vis.selectAll(«circle»).data(data);

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

1
circles.enter().append(«svg:circle»)

Далее мы определим некоторые свойства, такие как расстояние между центрами окружностей от осей X ( cx ) и Y ( cy ), их цвет, радиус и т. Д. Для извлечения cx и cy мы будем использовать xScale и yScale преобразовать год и подсчитать данные в графическое пространство и нарисовать круг в области SVG. Вот как будет выглядеть код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
var circles = vis.selectAll(«circle»).data(data);
circles.enter()
    .append(«svg:circle»)
     
    .attr(«stroke», «black») // sets the circle border
     
    .attr(«r», 10) // sets the radius
     
    .attr(«cx», function(d) { // transforms the year data so that it
        return xScale(d.year);
    })
     
    .attr(«cy», function(d) { // transforms the count data so that it
        return yScale(d.count);
    })
     
    .style(«fill», «red») // sets the circle color

Сохраните изменения и обновите страницу. Вы должны увидеть изображение ниже:

Диаграмма с нанесенными кругами

В первой части этой серии, когда мы выбирали данные из BigQuery, мы выбрали около 1000 слов.

1
SELECT word FROM [publicdata:samples.shakespeare] LIMIT 1000

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

Итак, войдите в Google BigQuery, и у нас будет экран, подобный показанному ниже:

Страница Google BigQuery с историей запросов

После того, как мы войдем в Google BigQuery, у нас будет интерфейс, где мы можем составлять и проверять наши SQL-запросы. Мы хотим выбрать, сколько раз конкретное слово появляется во всех произведениях Шекспира.

Итак, наш основной запрос будет выглядеть так:

1
SELECT SUM(word_count) as WCount,corpus_date FROM [publicdata:samples.shakespeare] WHERE word=»Caesar» GROUP BY corpus_date ORDER BY WCount

Приведенный выше запрос дает нам набор результатов, как показано ниже:

Google BigQuery запрос и результат

Давайте также включим группу работ, соответствующую количеству слов. Измените запрос, как показано, чтобы включить корпус:

1
SELECT SUM(word_count) as WCount,corpus_date,group_concat(corpus) as Work FROM [publicdata:samples.shakespeare] WHERE word=»Caesar» and corpus_date>0 GROUP BY corpus_date ORDER BY WCount

Результирующий набор результатов показан ниже:

Набор результатов Google BigQuery

Затем откройте app.py и создайте новый класс с именем GetChartData . Внутри него добавьте оператор запроса, который мы создали выше.

1
2
queryData = {‘query’:’SELECT SUM(word_count) as WCount,corpus_date,group_concat(corpus) as Work FROM ‘
‘[publicdata:samples.shakespeare] WHERE word=»God» and corpus_date>0 GROUP BY corpus_date ORDER BY WCount’}

Затем создайте сервис BigQuery, для которого мы будем выполнять наши queryData .

1
tableData = bigquery_service.jobs()

Теперь выполните queryData для службы BigQuery и распечатайте результат на странице.

1
2
dataList = tableData.query(projectId=PROJECT_NUMBER,body=queryData).execute()
self.response.out.write(dataList)

Также GetChartData новый маршрут для GetChartData как показано.

1
2
3
4
5
6
7
application = webapp2.WSGIApplication([
    (‘/chart’,ShowChartPage),
    (‘/displayChart’,DisplayChart),
    (‘/displayChart3’,DisplayChart3),
    (‘/getChartData’,GetChartData),
    (‘/’, ShowHome),
], debug=True)

Наконец обновите код для платформы GAE.

1
./appcfg.py update PythonD3jsMashup_Part2/

Укажите в браузере http://YourAppspotUrl.com/getChartData, который должен отображать полученные данные из BigQuery.

Далее мы попытаемся проанализировать данные, полученные от Google BigQuery, преобразовать их в объект данных JSON и передать их на клиентскую сторону для обработки с использованием D3.js.

Сначала мы проверим, есть ли какие-либо строки в возвращенном dataList . Если строк нет, мы установим ответ как ноль или ноль.

1
2
3
4
if ‘rows’ in dataList:
  # parse dataList
else:
  resp.append({‘count’:’0′,’year’:’0′,’corpus’:’0′})

Далее мы проанализируем dataList , зацикливая каждую строку и подбирая количество, год и корпус и создавая требуемый объект JSON.

01
02
03
04
05
06
07
08
09
10
resp = []
if ‘rows’ in dataList:
  for row in dataList[‘rows’]:
    for key,dict_list in row.iteritems():
        count = dict_list[0]
        year = dict_list[1]
        corpus = dict_list[2]
        resp.append({‘count’: count[‘v’],’year’:year[‘v’],’corpus’:corpus[‘v’]})
else:
  resp.append({‘count’:’0′,’year’:’0′,’corpus’:’0′})

Поскольку мы будем возвращать проанализированные данные как JSON, импортируйте библиотеку JSON

1
import json

И вернуть созданный ответ как ответ JSON.

1
2
self.response.headers[‘Content-Type’] = ‘application/json’
self.response.out.write(json.dumps(resp))

Давайте также сделаем ключевое слово для поиска динамическим, чтобы его можно было передавать в качестве параметра.

1
2
3
inputData = self.request.get(«inputData»)
queryData = {‘query’:’SELECT SUM(word_count) as WCount,corpus_date,group_concat(corpus) as Work FROM ‘
‘[publicdata:samples.shakespeare] WHERE word=»‘+inputData+'» and corpus_date>0 GROUP BY corpus_date ORDER BY WCount’}

Вот как класс GetChartData выглядит окончательно:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
class GetChartData(webapp2.RequestHandler):
  def get(self):
    inputData = self.request.get(«inputData»)
    queryData = {‘query’:’SELECT SUM(word_count) as WCount,corpus_date,group_concat(corpus) as Work FROM ‘
‘[publicdata:samples.shakespeare] WHERE word=»‘+inputData+'» GROUP BY corpus_date ORDER BY WCount’}
    tableData = bigquery_service.jobs()
    dataList = tableData.query(projectId=PROJECT_NUMBER,body=queryData).execute()
     
    resp = []
    if ‘rows’ in dataList:
      for row in dataList[‘rows’]:
        for key,dict_list in row.iteritems():
          count = dict_list[0]
          year = dict_list[1]
          corpus = dict_list[2]
          resp.append({‘count’: count[‘v’],’year’:year[‘v’],’corpus’:corpus[‘v’]})
    else:
      resp.append({‘count’:’0′,’year’:’0′,’corpus’:’0′})
     
      
    self.response.headers[‘Content-Type’] = ‘application/json’
    self.response.out.write(json.dumps(resp))

Обновите приложение в GAE и укажите в браузере http://YourAppspotUrl.com/getChartData, и вы увидите возвращенный ответ JSON.

Возвращенный ответ JSON

Далее мы создадим интерфейс для динамического запроса набора данных Google BigQuery из нашего приложения. Откройте Templates/displayChart_3.html и Templates/displayChart_3.html поле ввода, где мы будем вводить ключевые слова для запроса набора данных.

1
2
3
<div align=»center»>
    <input id=»txtKeyword» type=»text» class=»span3″ placeholder=»Type something…»>
</div>

Включите скрипт jQuery на странице и в случае события DOM ready, мы будем запрашивать Python метод GetChartData при нажатии Enter Key .

1
2
3
4
5
6
7
8
$(document).ready(function() {
    $(«#txtKeyword»).keyup(function(event) {
        if (event.keyCode == 13) { // If enter key press
            DisplayChart();
        }
    });
    InitChart();
});

Создайте еще одну функцию DisplayChart на стороне клиента, внутри которой мы сделаем Ajax-вызов метода Python GetChartData .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
function DisplayChart() {
    var keyword = $(‘#txtKeyword’).val();
    $.ajax({
        type: «GET»,
        url: «/getChartData»,
        data: {
            inputData: keyword
        },
        dataType: «json»,
        success: function(response) {
            console.log(response);
        },
        error: function(xhr, errorType, exception) {
            console.log(‘Error occured’);
        }
    });
}

Обновите код до GAE и укажите в своем браузере http://YourAppspotUrl.com/displayChart3 . Введите ключевое слово, скажите Caesar и нажмите Enter . Проверьте консоль браузера, и вы должны увидеть ответ JSON.

Далее, давайте построим круги, используя возвращенный ответ. Поэтому создайте еще одну функцию JavaScript под названием CreateChart . Эта функция похожа на функцию InitChart но данные будут переданы в качестве параметра. Вот как это выглядит:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
function CreateChart(data) {
    var vis = d3.select(«#visualisation»),
        WIDTH = 1000,
        HEIGHT = 500,
        MARGINS = {
            top: 20,
            right: 20,
            bottom: 20,
            left: 50
        },
        xScale = d3.scale.linear().range([MARGINS.left, WIDTH — MARGINS.right]).domain([d3.min(data, function(d) {
                return (parseInt(d.year, 10) — 5);
            }),
            d3.max(data, function(d) {
                return parseInt(d.year, 10);
            })
        ]),
        yScale = d3.scale.linear().range([HEIGHT — MARGINS.top, MARGINS.bottom]).domain([d3.min(data, function(d) {
                return (parseInt(d.count, 10) — 5);
            }),
            d3.max(data, function(d) {
                return parseInt(d.count, 10);
            })
        ]),
        xAxis = d3.svg.axis()
        .scale(xScale),
 
        yAxis = d3.svg.axis()
        .scale(yScale)
        .orient(«left»);
 
 
 
 
    vis.append(«svg:g»)
        .attr(«class», «x axis»)
        .attr(«transform», «translate(0,» + (HEIGHT — MARGINS.bottom) + «)»)
        .call(xAxis);
 
    vis.append(«svg:g»)
        .attr(«class», «y axis»)
        .attr(«transform», «translate(» + (MARGINS.left) + «,0)»)
        .call(yAxis);
 
 
    var circles = vis.selectAll(«circle»).data(data);
    circles.enter()
        .append(«svg:circle»)
        .attr(«stroke», «black»)
        .attr(«r», 10)
        .attr(«cx», function(d) {
            return xScale(d.year);
        })
        .attr(«cy», function(d) {
            return yScale(d.count);
        })
        .style(«fill», «red»)
 
}

Из функции InitChart удалите часть создания круга, так как теперь она не потребуется. Вот как выглядит InitChart :

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
function InitChart() {
    var data = [{
        «count»: «202»,
        «year»: «1590»
    }, {
        «count»: «215»,
        «year»: «1592»
    }, {
        «count»: «179»,
        «year»: «1593»
    }, {
        «count»: «199»,
        «year»: «1594»
    }, {
        «count»: «134»,
        «year»: «1595»
    }, {
        «count»: «176»,
        «year»: «1596»
    }, {
        «count»: «172»,
        «year»: «1597»
    }, {
        «count»: «161»,
        «year»: «1598»
    }, {
        «count»: «199»,
        «year»: «1599»
    }, {
        «count»: «181»,
        «year»: «1600»
    }, {
        «count»: «157»,
        «year»: «1602»
    }, {
        «count»: «179»,
        «year»: «1603»
    }, {
        «count»: «150»,
        «year»: «1606»
    }, {
        «count»: «187»,
        «year»: «1607»
    }, {
        «count»: «133»,
        «year»: «1608»
    }, {
        «count»: «190»,
        «year»: «1609»
    }, {
        «count»: «175»,
        «year»: «1610»
    }, {
        «count»: «91»,
        «year»: «1611»
    }, {
        «count»: «150»,
        «year»: «1612»
    }];
 
 
    var color = d3.scale.category20();
    var vis = d3.select(«#visualisation»),
        WIDTH = 1000,
        HEIGHT = 500,
        MARGINS = {
            top: 20,
            right: 20,
            bottom: 20,
            left: 50
        },
        xScale = d3.scale.linear().range([MARGINS.left, WIDTH — MARGINS.right]).domain([d3.min(data, function(d) {
                return (parseInt(d.year, 10) — 5);
            }),
            d3.max(data, function(d) {
                return parseInt(d.year, 10);
            })
        ]),
        yScale = d3.scale.linear().range([HEIGHT — MARGINS.top, MARGINS.bottom]).domain([d3.min(data, function(d) {
                return (parseInt(d.count, 10) — 5);
            }),
            d3.max(data, function(d) {
                return parseInt(d.count, 10);
            })
        ]),
        xAxis = d3.svg.axis()
        .scale(xScale),
 
        yAxis = d3.svg.axis()
        .scale(yScale)
        .orient(«left»);
 
 
 
 
    vis.append(«svg:g»)
        .attr(«class», «x axis»)
        .attr(«transform», «translate(0,» + (HEIGHT — MARGINS.bottom) + «)»)
        .call(xAxis);
 
    vis.append(«svg:g»)
        .attr(«class», «y axis»)
        .attr(«transform», «translate(» + (MARGINS.left) + «,0)»)
        .call(yAxis);
}

С этого момента, когда мы загружаем страницу /displayChart3 , круги не будут отображаться. Круги появятся только после поиска по ключевому слову. Итак, при успешном DisplayChart вызове вызова Ajax DisplayChart передайте ответ функции CreateChart .

1
2
3
4
success: function(response) {
    console.log(response);
    CreateChart(response);
}

Обновите код до GAE и попробуйте найти ключевое слово Caesar . Итак, теперь мы видим результат в виде кружков на графике. Но есть одна проблема: обе оси перезаписываются.

Диаграмма с перекрывающимися осями

Поэтому, чтобы избежать этого, мы проверим внутри функции CreateChart ли оси там или нет.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
var hasAxis = vis.select(‘.axis’)[0][0];
 
if (!hasAxis) {
    
    vis.append(«svg:g»)
        .attr(«class», «x axis»)
        .attr(«transform», «translate(0,» + (HEIGHT — MARGINS.bottom) + «)»)
        .call(xAxis);
 
    vis.append(«svg:g»)
        .attr(«class», «y axis»)
        .attr(«transform», «translate(» + (MARGINS.left) + «,0)»)
        .call(yAxis);
}

Как видите, мы только что проверили, есть ли у элемента SVG оси, и если нет, мы создадим их снова. Обновите код до GAE и попробуйте снова найти ключевое слово, и вы должны увидеть что-то вроде этого:

Окончательный график с правильно отображенными осями

Хотя сейчас все выглядит хорошо, все же есть несколько проблем, которые мы рассмотрим в следующей части этого урока. Мы также представим переходы D3.js и еще несколько функций на нашем графике D3.js и попытаемся сделать его более интерактивным.

Исходный код этого руководства доступен на GitHub .