В этом уроке я расскажу построение базового приложения приборной панели с Cube.js и самой популярной библиотеки для визуализации данных — D3.js . Хотя Cube.js не предоставляет сам уровень визуализации, его очень легко интегрировать с любой существующей библиотекой графиков. Кроме того, вы можете использовать шаблоны Cube.js для создания приложения-интерфейса с вашей любимой библиотекой диаграмм, средой-интерфейсом и комплектом пользовательского интерфейса. Механизм скаффолдинга свяжет все это вместе и настроит его для работы с бэкэндом Cube.js.
Вы можете проверить онлайн-демонстрацию этой панели инструментов здесь, а полный исходный код примера приложения доступен на Github .
Мы собираемся использовать Postgres для хранения наших данных. Cube.js подключится к нему и станет промежуточным ПО между базой данных и клиентом, предоставляя наш API, абстракцию, кэширование и многое другое. На внешнем интерфейсе у нас будет React with Material UI и D3 для рендеринга диаграммы. Ниже вы можете найти схему всей архитектуры примера приложения.
Если у вас есть какие-либо вопросы при просмотре этого руководства, присоединяйтесь к сообществу Slack и оставляйте там свой вопрос
Счастливого взлома!
Вам также может понравиться: Cube.js :
полное руководство по платформе с открытым исходным кодом.
Настройка базы данных и Cube.js
Первое, что нам нужно иметь — это база данных. Мы будем использовать Postgres для этого урока. Тем не менее, вы можете использовать вашу любимую базу данных SQL (или Mongo). Пожалуйста, обратитесь к документации Cube.js о том, как подключиться к различным базам данных .
Если у вас нет данных для панели инструментов, вы можете загрузить наш примерный набор данных Postgres для электронной коммерции.
Оболочка
1
$ curl http://cube.dev/downloads/ecom-dump-d3-example.sql > ecom-dump.sql
2
$ createdb ecom
3
$ psql --dbname ecom -f ecom-dump.sql
Теперь, когда у нас есть данные в базе данных, мы готовы создать бэкэнд-сервис Cube.js. Выполните следующие команды в вашем терминале:
Оболочка
xxxxxxxxxx
1
$ npm install -g cubejs-cli
2
$ cubejs create d3-dashboard -d postgres
Приведенные выше команды устанавливают CLI Cube.js и создают новый сервис, настроенный для работы с базой данных Postgres.
Cube.js использует переменные среды для конфигурации. Он использует переменные среды, начинающиеся с CUBEJS_
. Чтобы настроить соединение с нашей базой данных, нам нужно указать тип и имя БД. В папке проекта Cube.js замените содержимое файла .env следующим:
Оболочка
xxxxxxxxxx
1
CUBEJS_API_SECRET=SECRET
2
CUBEJS_DB_TYPE=postgres
3
CUBEJS_DB_NAME=ecom
4
CUBEJS_WEB_SOCKETS=true
Теперь давайте запустим сервер и откроем площадку для разработчиков по адресу http: // localhost: 4000 .
Оболочка
xxxxxxxxxx
1
$ npm run dev
Следующим шагом является создание схемы данных Cube.js . Cube.js использует схему данных для генерации SQL, который будет выполняться в вашей базе данных. Cube.js Playground может генерировать простые схемы на основе таблиц базы данных. Давайте перейдем на страницу Schema и сгенерируем схемы, которые нам нужны для нашей панели инструментов. Выберите line_items
, orders
, products
, product_categories
и users
таблицу и нажмите кнопку Сформировать схемы .
Давайте проверим нашу недавно сгенерированную схему. Перейдите на страницу Build и выберите меру в раскрывающемся списке. Вы должны увидеть простую линейную диаграмму. Вы можете выбрать D3 в раскрывающемся списке библиотеки графиков, чтобы увидеть пример визуализации D3. Обратите внимание, что это всего лишь пример, и вы всегда можете настроить и расширить его.
Теперь давайте внесем некоторые изменения в нашу схему. Генерация схемы облегчает начало работы и тестирование набора данных, но для реальных случаев использования нам почти всегда нужно вносить изменения вручную.
В схеме мы определяем меры и измерения и то, как они отображаются в запросы SQL. Вы можете найти обширную документацию по схеме данных здесь . Мы собираемся добавить priceRange
измерение в Orders
куб. Он будет указывать, попадает ли общая цена заказа в одну из групп: «0–100 долл. США», «100–200 долл. США», «200 долл. США +».
Для этого нам сначала нужно определить price
размерность для заказа. В нашей базе данных orders
нет ценового столбца, но мы можем рассчитать его на основе общей цены line_items
внутреннего заказа. Наша схема уже автоматически указывается и определяется связь между Orders
и LineTimes
кубами. Вы можете прочитать больше о присоединениях здесь .
JavaScript
xxxxxxxxxx
1
// You can check the belongsTo join
2
// to the Orders cube inside the LineItems cube
3
joins: {
4
Orders: {
5
sql: `${CUBE}.order_id = ${Orders}.id`,
6
relationship: `belongsTo`
7
}
8
}
LineItems
Куб имеет price
меру с sum
типом. Мы можем ссылаться на эту меру из Orders
куба как на измерение, и оно даст нам сумму всех позиций, принадлежащих этому порядку. Это называется subQuery
измерением; Вы можете узнать больше об этом здесь .
JavaScript
xxxxxxxxxx
1
// Add the following dimension to the Orders cube
2
price: {
3
sql: `${LineItems.price}`,
4
subQuery: true,
5
type: `number`,
6
format: `currency`
7
}
Теперь, основываясь на этом измерении, мы можем создать priceRange
измерение. Мы будем использовать оператор case для определения условной логики для наших ценовых сегментов.
JavaScript
1
// Add the following dimension to the Orders cube
2
priceRange: {
3
type: `string`,
4
case: {
5
when: [
6
{ sql: `${price} < 101`, label: `$0 - $100` },
7
{ sql: `${price} < 201`, label: `$100 - $200` }
8
],
9
else: {
10
label: `$200+`
11
}
12
}
13
}
Давайте попробуем наше недавно созданное измерение! Перейдите на страницу Build на детской площадке, выберите Orders
меру подсчета с измерением Orders
ценового диапазона. Вы всегда можете проверить сгенерированный SQL, нажав кнопку SQL на панели управления.
Вот и все для бэкэнда! В следующей части мы рассмотрим подробнее, как визуализировать результаты наших запросов с помощью D3.
Диаграмма рендеринга с D3.js
Теперь, когда мы можем построить нашу первую диаграмму, давайте проверим пример кода, который игровая площадка использует для визуализации с помощью D3. Перед этим нам нужно понять, как Cube.js принимает и обрабатывает запрос и возвращает результат обратно.
Запрос Cube.js представляет собой простой объект JSON, содержащий несколько свойств. Основные свойства запроса являются measures
, dimensions
, timeDimensions
, и filters
. Вы можете узнать больше о формате запроса JSON Cube.js и его свойствах здесь . Вы всегда можете проверить JSON-запрос на игровой площадке, нажав кнопку JSON Query рядом с селектором диаграммы.
Бэкэнд Cube.js принимает этот запрос, а затем использует его и созданную ранее схему для генерации SQL-запроса. Этот SQL-запрос будет выполнен в нашей базе данных, а результат будет отправлен обратно клиенту.
Хотя Cube.js можно запрашивать через простой HTTP REST API, мы собираемся использовать клиентскую библиотеку Cube.js JavaScript. Помимо прочего, он предоставляет полезные инструменты для обработки данных после их возвращения из серверной части.
После загрузки данных клиент Cube.js создает ResultSet
объект, который предоставляет набор методов для доступа к данным и манипулирования ими. Мы собираемся использовать два из них сейчас: ResultSet.series
и ResultSet.chartPivot
. Вы можете узнать обо всех возможностях клиентской библиотеки Cube.js в документации .
ResultSet.series
Метод возвращает массив рядов данных с данными ключа, названия и серии. Метод принимает один аргумент pivotConfig
. Это объект, содержащий правила о том, как данные должны поворачиваться; мы поговорим об этом немного. На линейном графике каждая серия обычно представлена отдельной линией. Этот метод полезен для подготовки данных в формате, ожидаемом D3.
JavaScript
xxxxxxxxxx
1
// For query
2
{
3
measures: ['Stories.count'],
4
timeDimensions: [{
5
dimension: 'Stories.time',
6
dateRange: ['2015-01-01', '2015-12-31'],
7
granularity: 'month'
8
}]
9
}
10
// ResultSet.series() will return
12
[
13
{
14
"key":"Stories.count",
15
"title": "Stories Count",
16
"series": [
17
{ "x":"2015-01-01T00:00:00", "value": 27120 },
18
{ "x":"2015-02-01T00:00:00", "value": 25861 },
19
{ "x": "2015-03-01T00:00:00", "value": 29661 },
20
//...
21
]
22
}
23
]
Следующий метод нам нужен ResultSet.chartPivot
. Он принимает тот же pivotConfig
аргумент и возвращает массив данных со значениями для оси X и для каждой имеющейся у нас серии.
JavaScript
xxxxxxxxxx
1
// For query
2
{
3
measures: ['Stories.count'],
4
timeDimensions: [{
5
dimension: 'Stories.time',
6
dateRange: ['2015-01-01', '2015-12-31'],
7
granularity: 'month'
8
}]
9
}
10
// ResultSet.chartPivot() will return
12
[
13
{ "x":"2015-01-01T00:00:00", "Stories.count": 27120 },
14
{ "x":"2015-02-01T00:00:00", "Stories.count": 25861 },
15
{ "x": "2015-03-01T00:00:00", "Stories.count": 29661 },
16
//...
17
]
Как упомянуто выше, pivotConfig
аргумент является объектом, используемым для управления тем, как преобразовать или повернуть данные. Объект имеет два свойства: x
и y
оба являются массивами. Добавляя меры или измерения к одному из них, вы можете контролировать то, что идет к оси X, а что к оси Y. Для запроса с одним measure
и одним timeDimension
, pivotConfig
имеет следующее значение по умолчанию:
JavaScript
xxxxxxxxxx
1
{
2
x: `CubeName.myTimeDimension.granularity`,
3
y: `measures`
4
}
Здесь «меры» - это особое значение, означающее, что все меры должны идти по оси Y. В большинстве случаев значение по умолчанию pivotConfig
должно работать нормально. В следующей части я покажу вам, когда и как мы должны это изменить.
Теперь давайте посмотрим на код, который генерирует игровой интерфейс, когда мы выбираем диаграмму D3. Выберите меру на игровой площадке и измените тип визуализации на D3. Затем нажмите кнопку « Код» , чтобы проверить код внешнего интерфейса для отображения диаграммы.
Вот полный исходный код с этой страницы:
JavaScript
xxxxxxxxxx
1
import React from 'react';
2
import cubejs from '@cubejs-client/core';
3
import { QueryRenderer } from '@cubejs-client/react';
4
import { Spin } from 'antd';
5
import * as d3 from 'd3';
7
const COLORS_SERIES = ['#FF6492', '#141446', '#7A77FF'];
8
const draw = (node, resultSet, chartType) => {
10
// Set the dimensions and margins of the graph
11
const margin = {top: 10, right: 30, bottom: 30, left: 60},
12
width = node.clientWidth - margin.left - margin.right,
13
height = 400 - margin.top - margin.bottom;
14
d3.select(node).html("");
16
const svg = d3.select(node)
17
.append("svg")
18
.attr("width", width + margin.left + margin.right)
19
.attr("height", height + margin.top + margin.bottom)
20
.append("g")
21
.attr("transform",
22
"translate(" + margin.left + "," + margin.top + ")");
23
// Prepare data in D3 format
25
const data = resultSet.series().map((series) => ({
26
key: series.title, values: series.series
27
}));
28
// color palette
30
const color = d3.scaleOrdinal()
31
.domain(data.map(d => d.key ))
32
.range(COLORS_SERIES)
33
// Add X axis
35
const x = d3.scaleTime()
36
.domain(d3.extent(resultSet.chartPivot(), c => d3.isoParse(c.x)))
37
.range([ 0, width ]);
38
svg.append("g")
39
.attr("transform", "translate(0," + height + ")")
40
.call(d3.axisBottom(x));
41
// Add Y axis
43
const y = d3.scaleLinear()
44
.domain([0, d3.max(data.map((s) => d3.max(s.values, (i) => i.value)))])
45
.range([ height, 0 ]);
46
svg.append("g")
47
.call(d3.axisLeft(y));
48
// Draw the lines
50
svg.selectAll(".line")
51
.data(data)
52
.enter()
53
.append("path")
54
.attr("fill", "none")
55
.attr("stroke", d => color(d.key))
56
.attr("stroke-width", 1.5)
57
.attr("d", (d) => {
58
return d3.line()
59
.x(d => x(d3.isoParse(d.x)))
60
.y(d => y(+d.value))
61
(d.values)
62
})
63
}
65
const lineRender = ({ resultSet }) => (
67
<div ref={el => el && draw(el, resultSet, 'line')} />
68
)
69
const API_URL = "http://localhost:4000"; // change to your actual endpoint
72
const cubejsApi = cubejs(
74
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1NzkwMjU0ODcsImV4cCI6MTU3OTExMTg4N30.nUyJ4AEsNk9ks9C8OwGPCHrcTXyJtqJxm02df7RGnQU",
75
{ apiUrl: API_URL + "/cubejs-api/v1" }
76
);
77
const renderChart = (Component) => ({ resultSet, error }) => (
79
(resultSet && <Component resultSet={resultSet} />) ||
80
(error && error.toString()) ||
81
(<Spin />)
82
)
83
const ChartRenderer = () => <QueryRenderer
85
query={{
86
"measures": [
87
"Orders.count"
88
],
89
"timeDimensions": [
90
{
91
"dimension": "Orders.createdAt",
92
"granularity": "month"
93
}
94
],
95
"filters": []
96
}}
97
cubejsApi={cubejsApi}
98
render={renderChart(lineRender)}
99
/>;
100
export default ChartRenderer;
Компонент React, который отображает диаграмму, представляет собой одну строку draw
, заключающую в себе функцию, которая выполняет всю работу.
JavaScript
xxxxxxxxxx
1
const lineRender = ({ resultSet }) => (
2
<div ref={el => el && draw(el, resultSet, 'line')} />
3
)
В этой draw
функции много чего происходит . Хотя он уже отображает диаграмму, подумайте о ней как об примере и хорошей отправной точке для настройки. В следующей части мы будем работать над нашей собственной панелью мониторинга, и я покажу вам, как это сделать.
Не стесняйтесь нажимать кнопку « Редактировать» и поиграть с кодом в песочнице кода.
Создание панели интерфейса пользователя
Теперь мы готовы создать наше веб-приложение. Мы собираемся использовать шаблоны Cube.js, которые являются механизмом скаффолдинга для быстрого создания приложений веб-интерфейса, настроенных для работы с бэкэндом Cube.js. Он предоставляет выбор различных инфраструктур веб-интерфейса, наборов пользовательского интерфейса и библиотек диаграмм для смешивания. Мы выберем React, Material UI и D3.js. Давайте перейдем на вкладку «Приложение панели мониторинга» и создадим новое приложение панели мониторинга.
Генерация приложения и установка всех зависимостей могут занять несколько минут. Как только это будет сделано, у вас будет dashboard-app
папка в папке проекта Cube.js. Чтобы запустить приложение внешнего интерфейса, перейдите на вкладку «Приложение Dashboard» на игровой площадке и нажмите кнопку «Пуск» или выполните следующую команду в папке «Dashboard-app»:
Оболочка
xxxxxxxxxx
1
$ npm start
Убедитесь, что бэкэнд-процесс Cube.js запущен и работает, так как наше приложение использует его API. Приложение внешнего интерфейса работает на http: // localhost: 3000 . Если вы откроете его в своем браузере, вы должны увидеть пустую панель инструментов.
Чтобы добавить диаграмму на приборную панель, мы можем либо построить ее на игровой площадке и нажать кнопку « Добавить на приборную панель» , либо отредактировать src/pages/DashboardPage.js
файл в dashboard-app
папке. Давайте перейдем к последнему варианту. Среди прочего, этот файл объявляет DashboardItems
переменную, которая является массивом запросов для диаграмм.
Изменить, dashboard-app/src/pages/DashboardPage.js
чтобы добавить диаграммы на приборную панель.
разница
xxxxxxxxxx
1
-const DashboardItems = [];
2
+const DashboardItems = [
3
+ {
4
+ id: 0,
5
+ name: "Orders last 14 days",
6
+ vizState: {
7
+ query: {
8
+ measures: ["Orders.count"],
9
+ timeDimensions: [
10
+ {
11
+ dimension: "Orders.createdAt",
12
+ granularity: "day",
13
+ dateRange: "last 14 days"
14
+ }
15
+ ],
16
+ filters: []
17
+ },
18
+ chartType: "line"
19
+ }
20
+ },
21
+ {
22
+ id: 1,
23
+ name: "Orders Status by Customers City",
24
+ vizState: {
25
+ query: {
26
+ measures: ["Orders.count"],
27
+ dimensions: ["Users.city", "Orders.status"],
28
+ timeDimensions: [
29
+ {
30
+ dimension: "Orders.createdAt",
31
+ dateRange: "last year"
32
+ }
33
+ ]
34
+ },
35
+ chartType: "bar",
36
+ pivotConfig: {
37
+ x: ["Users.city"],
38
+ y: ["Orders.status", "measures"]
39
+ }
40
+ }
41
+ },
42
+ {
43
+ id: 3,
44
+ name: "Orders by Product Categories Over Time",
45
+ vizState: {
46
+ query: {
47
+ measures: ["Orders.count"],
48
+ timeDimensions: [
49
+ {
50
+ dimension: "Orders.createdAt",
51
+ granularity: "month",
52
+ dateRange: "last year"
53
+ }
54
+ ],
55
+ dimensions: ["ProductCategories.name"]
56
+ },
57
+ chartType: "area"
58
+ }
59
+ },
60
+ {
61
+ id: 3,
62
+ name: "Orders by Price Range",
63
+ vizState: {
64
+ query: {
65
+ measures: ["Orders.count"],
66
+ filters: [
67
+ {
68
+ "dimension": "Orders.price",
69
+ "operator": "set"
70
+ }
71
+ ],
72
+ dimensions: ["Orders.priceRange"]
73
+ },
74
+ chartType: "pie"
75
+ }
76
+ }
77
+];
Как вы можете видеть выше, мы только что добавили массив объектов запроса Cube.js.
Если вы обновите панель управления, вы сможете увидеть свои графики!
Вы можете заметить, что один из наших запросов имеет pivotConfig
следующее определение:
JavaScript
xxxxxxxxxx
1
pivotConfig: {
2
x: ["Users.city"],
3
y: ["Orders.status", "measures"]
4
}
Как я упоминал в предыдущем разделе, значение по умолчанию для pivotConfig
обычно работает нормально, но в некоторых случаях, таких как этот, нам нужно настроить его, чтобы получить желаемый результат. Мы хотим построить здесь гистограмму с городами на оси X и количеством заказов на оси Y, сгруппированных по статусам заказов. То есть именно то , что мы передаем здесь в pivotConfig
: Users.city
на ось X и меры с Orders.status
к Y-оси , чтобы получить сгруппированный результат.
Чтобы настроить рендеринг графиков, вы можете отредактировать dashboard-app/src/pages/ChartRenderer.js
файл. Это должно выглядеть знакомо тому, что мы видели в предыдущей части.
Вы можете проверить онлайн-демонстрацию этой панели инструментов здесь, а полный исходный код примера приложения доступен на Github .
Поздравляем с завершением этого руководства!
Я хотел бы услышать от вас о вашем опыте изучения этого урока. Пожалуйста, присылайте любые комментарии или отзывы, которые вы можете иметь здесь, в комментариях или в этом сообществе Slack . Спасибо и надеюсь, что вы нашли это руководство полезным!