Cube.js는 분석 프레임 워크를 제공하고 D3은 시각화를 제공합니다.
https://dev.to/keydunov/d3-dashboard-tutorial-with-cube-js-ehb
이 자습서에서는 Cube.js와 가장 널리 사용되는 데이터 시각화 라이브러리인 D3.js를 사용하여 기본 대시 보드 응용 프로그램을 작성하는 방법을 다룹니다.
Cube.js는 시각화 계층 자체를 제공하지 않지만 기존 차트 라이브러리와 쉽게 통합 할 수 있습니다.
또한 Cube.js 템플릿을 사용하여 선호하는 차트 라이브러리, 프론트 엔드 프레임 워크 및 UI 키트로 프론트 엔드 애플리케이션을 스캐폴드 할 수 있습니다.
스캐폴딩 엔진은 이를 모두 연결하고 Cube.js 백엔드와 작동하도록 구성합니다.
이 대시 보드의 온라인 데모를 여기서 확인할 수 있으며 예제 앱의 전체 소스 코드는 Github에서 제공됩니다.
Postgres를 사용하여 데이터를 저장합니다. Cube.js는 여기에 연결하여 데이터베이스와 클라이언트 간의 미들웨어 역할을 하여 API, 추상화, 캐싱 등을 제공합니다.
프런트 엔드에는 차트 렌더링을 위해 재질 UI 및 D3을 사용하여 반응합니다.
아래에서 예제 앱의 전체 아키텍처에 대한 스키마를 찾을 수 있습니다.
이 가이드를 진행하는 동안 질문이 있으시면 언제든지 이 슬랙 커뮤니티에 가입하여 질문을 게시하십시오.
행복한 해킹! ?
데이터베이스 및 Cube.js 설정
가장 먼저 필요한 것은 데이터베이스입니다. 이 자습서에서는 Postgres를 사용합니다.
그러나 선호하는 SQL (또는 Mongo) 데이터베이스를 사용할 수 있습니다.
다른 데이터베이스에 연결하는 방법에 대해서는 Cube.js 문서를 참조하십시오.
대시 보드에 대한 데이터가 없는 경우 샘플 전자 상거래 Postgres 데이터 세트를 로드 할 수 있습니다.
$ curl http://cube.dev/downloads/ecom-dump-d3-example.sql > ecom-dump.sql
$ createdb ecom
$ psql --dbname ecom -f ecom-dump.sql
이제 데이터베이스에 데이터가 있으면 Cube.js 백엔드 서비스를 만들 준비가 되었습니다. 터미널에서 다음 명령을 실행하십시오.
$ npm install -g cubejs-cli
$ cubejs create d3-dashboard -d postgres
위의 명령은 Cube.js CLI를 설치하고 Postgres 데이터베이스와 작동하도록 구성된 새 서비스를 만듭니다.
Cube.js는 구성에 환경 변수를 사용합니다. CUBEJS_로 시작하는 환경 변수를 사용합니다.
데이터베이스 연결을 구성하려면 DB 유형과 이름을 지정해야 합니다.
Cube.js 프로젝트 폴더에서 .env의 컨텐츠를 다음으로 바꾸십시오.
CUBEJS_API_SECRET=SECRET
CUBEJS_DB_TYPE=postgres
CUBEJS_DB_NAME=ecom
CUBEJS_WEB_SOCKETS=true
이제 서버를 시작하고 http : // localhost : 4000에서 개발자 놀이터를 엽니다.
$ npm run dev
다음 단계는 Cube.js 데이터 스키마를 작성하는 것입니다.
Cube.js는 데이터 스키마를 사용하여 데이터베이스에서 실행될 SQL 코드를 생성합니다.
Cube.js Playground는 데이터베이스 테이블을 기반으로 간단한 스키마를 생성 할 수 있습니다.
스키마 페이지로 이동하여 대시 보드에 필요한 스키마를 생성 해 봅시다.
line_items, order, products, product_categories 및 users 테이블을 선택하고 스키마 생성을 클릭하십시오.
새로 생성 된 스키마를 테스트 해 봅시다. 빌드 페이지로 이동하여 드롭 다운에서 측정 값을 선택하십시오. 간단한 꺾은 선형 차트를 볼 수 있어야 합니다. 차트 라이브러리 드롭 다운에서 D3을 선택하여 D3 시각화의 예를 볼 수 있습니다. 이것은 단지 예일 뿐이며 언제든지 사용자 정의하고 확장 할 수 있습니다.
이제 스키마를 업데이트하겠습니다. 스키마 생성을 통해 데이터 세트를 쉽게 시작하고 테스트 할 수 있지만 실제 사용 사례의 경우 거의 항상 수동으로 변경해야 합니다.
스키마에서 측정 값과 차원과 이들이 SQL 쿼리에 매핑 되는 방식을 정의합니다.
여기에서 데이터 스키마에 대한 광범위한 문서를 찾을 수 있습니다.
Orders 큐브에 priceRange 차원을 추가하겠습니다. 주문의 총 가격이 버킷 중 하나 인 "$ 0-$ 100", "$ 100-$ 200", "$ 200 +"인지 여부를 나타냅니다.
이를 위해서는 먼저 주문의 가격 차원을 정의해야 합니다. 데이터베이스에서 주문에는 가격 열이 없지만 주문 내 line_items의 총 가격을 기반으로 계산할 수 있습니다. 스키마는 이미 Orders와 LineTimes 큐브 간의 관계를 자동으로 표시하고 정의했습니다. 조인에 대한 자세한 내용은 여기를 참조하십시오.
// You can check the belongsTo join
// to the Orders cube inside the LineItems cube
joins: {
Orders: {
sql: `${CUBE}.order_id = ${Orders}.id`,
relationship: `belongsTo`
}
}
LineItems 큐브에는 합계 유형의 가격 측정이 있습니다. Orders 큐브에서 이 측정 값을 차원으로 참조 할 수 있으며 해당 주문에 속하는 모든 광고 항목의 합계를 제공합니다. 이를 하위 쿼리 측정 기준이라고 합니다. 여기에서 자세히 알아볼 수 있습니다.
// Add the following dimension to the Orders cube
price: {
sql: `${LineItems.price}`,
subQuery: true,
type: `number`,
format: `currency`
}
이제 이 차원을 기반으로 priceRange 차원을 만들 수 있습니다. 사례 명세서를 사용하여 가격 버킷에 대한 조건부 논리를 정의합니다.
// Add the following dimension to the Orders cube
priceRange: {
type: `string`,
case: {
when: [
{ sql: `${price} < 101`, label: `$0 - $100` },
{ sql: `${price} < 201`, label: `$100 - $200` }
],
else: {
label: `$200+`
}
}
}
새로 만든 차원을 사용해 봅시다! 놀이터의 빌드 페이지로 이동하여 주문 가격 범위 차원으로 주문 수 측정을 선택하십시오. 제어 막대에서 SQL 단추를 클릭하여 생성 된 SQL을 항상 확인할 수 있습니다.
이것이 백엔드입니다. 다음 부분에서는 D3을 사용하여 쿼리 결과를 렌더링 하는 방법에 대해 자세히 살펴 보겠습니다.
D3.js를 사용한 렌더링 차트
이제 첫 번째 차트를 만들 수 있으므로 놀이터에서 D3으로 렌더링 하는 데 사용하는 예제 코드를 살펴 보겠습니다.
그 전에 Cube.js가 쿼리를 수락하고 처리하고 결과를 반환하는 방법을 이해해야 합니다.
Cube.js 쿼리는 여러 속성을 포함하는 간단한 JSON 객체입니다.
쿼리의 주요 속성은 측정 값, 차원, 시간 차원 및 필터입니다.
Cube.js JSON 쿼리 형식 및 해당 속성에 대한 자세한 내용은 여기를 참조하십시오.
차트 선택기 옆에 있는 JSON 쿼리 버튼을 클릭하면 언제든지 놀이터에서 JSON 쿼리를 검사 할 수 있습니다.
Cube.js 백엔드는 이 조회를 승인 한 후 이를 사용하여 이전에 작성한 스키마를 사용하여 SQL 조회를 생성합니다. 이 SQL 쿼리는 데이터베이스에서 실행되고 결과는 클라이언트로 다시 전송됩니다.
일반 HTTP REST API를 통해 Cube.js를 쿼리 할 수 있지만 Cube.js JavaScript 클라이언트 라이브러리를 사용합니다. 무엇보다도 백엔드에서 데이터를 반환 한 후 데이터를 처리하는 유용한 도구를 제공합니다.
데이터가 로드 되면 Cube.js 클라이언트는 데이터에 액세스하고 조작 할 수 있는 메소드 세트를 제공하는 ResultSet 오브젝트를 작성합니다. 이제 ResultSet.series와 ResultSet.chartPivot 중 두 가지를 사용하겠습니다.
문서에서 Cube.js 클라이언트 라이브러리의 모든 기능에 대해 배울 수 있습니다.
ResultSet.series 메소드는 키, 제목 및 시리즈 데이터가 있는 데이터 시리즈 배열을 리턴 합니다. 이 메소드는 하나의 인수 인 pivotConfig를 허용합니다. 데이터 피벗 방법에 대한 규칙을 포함하는 개체입니다. 우리는 그것에 대해 조금 이야기 할 것입니다. 꺾은 선형 차트에서 각 계열은 일반적으로 별도의 선으로 표시됩니다. 이 방법은 D3에 필요한 형식으로 데이터를 준비하는 데 유용합니다.
// For query
{
measures: ['Stories.count'],
timeDimensions: [{
dimension: 'Stories.time',
dateRange: ['2015-01-01', '2015-12-31'],
granularity: 'month'
}]
}
// ResultSet.series() will return
[
{
"key":"Stories.count",
"title": "Stories Count",
"series": [
{ "x":"2015-01-01T00:00:00", "value": 27120 },
{ "x":"2015-02-01T00:00:00", "value": 25861 },
{ "x": "2015-03-01T00:00:00", "value": 29661 },
//...
]
}
]
다음으로 필요한 방법은 ResultSet.chartPivot입니다. 동일한 pivotConfig 인수를 허용하고 X 축 및 모든 시리즈에 대한 값을 가진 데이터 배열을 반환합니다.
// For query
{
measures: ['Stories.count'],
timeDimensions: [{
dimension: 'Stories.time',
dateRange: ['2015-01-01', '2015-12-31'],
granularity: 'month'
}]
}
// ResultSet.chartPivot() will return
[
{ "x":"2015-01-01T00:00:00", "Stories.count": 27120 },
{ "x":"2015-02-01T00:00:00", "Stories.count": 25861 },
{ "x": "2015-03-01T00:00:00", "Stories.count": 29661 },
//...
]
위에서 언급했듯이 pivotConfig 인수는 데이터를 변환하거나 피벗하는 방법을 제어하기 위한 개체입니다.
객체에는 x와 y라는 두 가지 속성이 있으며 둘 다 배열입니다.
측정 값 또는 치수를 그 중 하나에 추가하여 X 축으로 이동하는 것과 Y 축으로 이동하는 것을 제어 할 수 있습니다.
측정 값과 timeDimension이 하나 인 쿼리의 경우 pivotConfig의 기본값은 다음과 같습니다.
{
x: `CubeName.myTimeDimension.granularity`,
y: `measures`
}
여기서 '측정 값'은 특별한 값으로 모든 측정 값이 Y 축으로 이동해야 합니다. 대부분의 경우 pivotConfig의 기본값이 정상적으로 작동합니다. 다음 부분에서는 언제 어떻게 변경해야 하는지 보여 드리겠습니다.
이제 D3 차트를 선택할 때 놀이터에서 생성되는 프런트 엔드 코드를 살펴 보겠습니다.
놀이터에서 측정 값을 선택하고 시각화 유형을 D3으로 변경하십시오. 그런 다음 코드를 클릭하여 차트를 렌더링 할 프런트 엔드 코드를 검사하십시오.
해당 페이지의 전체 소스 코드는 다음과 같습니다.
import React from 'react';
import cubejs from '@cubejs-client/core';
import { QueryRenderer } from '@cubejs-client/react';
import { Spin } from 'antd';
import * as d3 from 'd3';
const COLORS_SERIES = ['#FF6492', '#141446', '#7A77FF'];
const draw = (node, resultSet, chartType) => {
// Set the dimensions and margins of the graph
const margin = {top: 10, right: 30, bottom: 30, left: 60},
width = node.clientWidth - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
d3.select(node).html("");
const svg = d3.select(node)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Prepare data in D3 format
const data = resultSet.series().map((series) => ({
key: series.title, values: series.series
}));
// color palette
const color = d3.scaleOrdinal()
.domain(data.map(d => d.key ))
.range(COLORS_SERIES)
// Add X axis
const x = d3.scaleTime()
.domain(d3.extent(resultSet.chartPivot(), c => d3.isoParse(c.x)))
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
const y = d3.scaleLinear()
.domain([0, d3.max(data.map((s) => d3.max(s.values, (i) => i.value)))])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// Draw the lines
svg.selectAll(".line")
.data(data)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", d => color(d.key))
.attr("stroke-width", 1.5)
.attr("d", (d) => {
return d3.line()
.x(d => x(d3.isoParse(d.x)))
.y(d => y(+d.value))
(d.values)
})
}
const lineRender = ({ resultSet }) => (
<div ref={el => el && draw(el, resultSet, 'line')} />
)
const API_URL = "http://localhost:4000"; // change to your actual endpoint
const cubejsApi = cubejs(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1NzkwMjU0ODcsImV4cCI6MTU3OTExMTg4N30.nUyJ4AEsNk9ks9C8OwGPCHrcTXyJtqJxm02df7RGnQU",
{ apiUrl: API_URL + "/cubejs-api/v1" }
);
const renderChart = (Component) => ({ resultSet, error }) => (
(resultSet && <Component resultSet={resultSet} />) ||
(error && error.toString()) ||
(<Spin />)
)
const ChartRenderer = () => <QueryRenderer
query={{
"measures": [
"Orders.count"
],
"timeDimensions": [
{
"dimension": "Orders.createdAt",
"granularity": "month"
}
],
"filters": []
}}
cubejsApi={cubejsApi}
render={renderChart(lineRender)}
/>;
export default ChartRenderer;
차트를 렌더링 하는 React 구성 요소는 전체 작업을 수행하는 그리기 기능을 감싸는 한 줄에 불과합니다.
const lineRender = ({ resultSet }) => (
<div ref={el => el && draw(el, resultSet, 'line')} />
)
이 그리기 기능에는 많은 일이 있습니다. 차트를 이미 렌더링 하지만 차트를 예로 들어 사용자 지정을 위한 좋은 출발점으로 생각하십시오. 다음 부분에서 자체 대시 보드에서 작업 할 때 이를 수행하는 방법을 보여 드리겠습니다.
편집 버튼을 클릭하고 코드 샌드 박스의 코드를 가지고 놀아주십시오.
프론트 엔드 대시 보드 구축
이제 프론트 엔드 응용 프로그램을 빌드 할 준비가 되었습니다.
Cube.js 백엔드와 작동하도록 구성된 프론트 엔드 애플리케이션을 빠르게 작성 하기 위한 스캐폴딩 엔진인 Cube.js 템플리트를 사용합니다.
서로 다른 프런트 엔드 프레임 워크, UI 키트 및 차트 라이브러리를 선택할 수 있습니다. React, Material UI, D3.js를 선택하겠습니다.
대시 보드 앱 탭으로 이동하여 새 대시 보드 응용 프로그램을 만듭니다.
앱을 생성하고 모든 종속성을 설치하는 데 몇 분이 걸릴 수 있습니다. 완료되면 Cube.js 프로젝트 폴더 안에 대시 보드 앱 폴더가 생깁니다. 프론트 엔드 애플리케이션을 시작하려면 놀이터의 "대시 보드 앱"탭으로 이동하여 "시작"버튼을 누르거나 dashboard-app 폴더 내에서 다음 명령을 실행하십시오.
$ npm start
프론트 엔드 애플리케이션이 API를 사용하므로 Cube.js 백엔드 프로세스가 시작되어 실행 중인지 확인하십시오.
프론트 엔드 애플리케이션이 http : // localhost : 3000에서 실행 중입니다. 브라우저에서 열면 빈 대시 보드가 표시됩니다.
대시 보드에 차트를 추가하려면 놀이터에서 차트를 작성하고 "대시 보드에 추가"버튼을 클릭하거나 dashboard-app 폴더에서 src / pages / DashboardPage.js 파일을 편집하십시오. 후자의 옵션으로 넘어 갑시다.
무엇보다도 이 파일은 차트에 대한 쿼리 배열 인 DashboardItems 변수를 선언합니다.
dashboard-app / src / pages / DashboardPage.js를 편집하여 차트를 대시 보드에 추가하십시오.
-const DashboardItems = [];
+const DashboardItems = [
+ {
+ id: 0,
+ name: "Orders last 14 days",
+ vizState: {
+ query: {
+ measures: ["Orders.count"],
+ timeDimensions: [
+ {
+ dimension: "Orders.createdAt",
+ granularity: "day",
+ dateRange: "last 14 days"
+ }
+ ],
+ filters: []
+ },
+ chartType: "line"
+ }
+ },
+ {
+ id: 1,
+ name: "Orders Status by Customers City",
+ vizState: {
+ query: {
+ measures: ["Orders.count"],
+ dimensions: ["Users.city", "Orders.status"],
+ timeDimensions: [
+ {
+ dimension: "Orders.createdAt",
+ dateRange: "last year"
+ }
+ ]
+ },
+ chartType: "bar",
+ pivotConfig: {
+ x: ["Users.city"],
+ y: ["Orders.status", "measures"]
+ }
+ }
+ },
+ {
+ id: 3,
+ name: "Orders by Product Categories Over Time",
+ vizState: {
+ query: {
+ measures: ["Orders.count"],
+ timeDimensions: [
+ {
+ dimension: "Orders.createdAt",
+ granularity: "month",
+ dateRange: "last year"
+ }
+ ],
+ dimensions: ["ProductCategories.name"]
+ },
+ chartType: "area"
+ }
+ },
+ {
+ id: 3,
+ name: "Orders by Price Range",
+ vizState: {
+ query: {
+ measures: ["Orders.count"],
+ filters: [
+ {
+ "dimension": "Orders.price",
+ "operator": "set"
+ }
+ ],
+ dimensions: ["Orders.priceRange"]
+ },
+ chartType: "pie"
+ }
+ }
+];
위에서 볼 수 있듯이 Cube.js 쿼리 객체 배열을 추가했습니다.
대시 보드를 새로 고치면 차트를 볼 수 있습니다!
쿼리 중 하나에 다음과 같이 pivotConfig가 정의되어 있음을 알 수 있습니다.
pivotConfig: {
x: ["Users.city"],
y: ["Orders.status", "measures"]
}
이전 부분에서 언급했듯이 pivotConfig의 기본값은 정상적으로 작동하지만 이와 같은 경우에는 원하는 결과를 얻도록 조정해야 합니다.
X 축의 도시와 주문 상태별로 그룹화 된 Y 축의 주문 수를 나타내는 막 대형 차트를 여기에 표시하려고 합니다.
이것이 정확하게 pivotConfig : Users.city에서 X 축으로 전달하고 Orders.status를 사용하여 Y 축으로 측정하여 그룹화 된 결과를 얻는 것입니다.
차트 렌더링을 사용자 정의하기 위해 dashboard-app / src / pages / ChartRenderer.js 파일을 편집 할 수 있습니다. 이전 부분에서 본 것에 익숙해 보일 것입니다.
이 대시 보드의 온라인 데모를 여기서 확인할 수 있으며 예제 앱의 전체 소스 코드는 Github에서 제공됩니다.
이 안내서를 완성한 것을 축하합니다! ?
이 안내서에 따른 귀하의 경험에 대해 귀하의 의견을 듣고 싶습니다 의견이나 슬랙 커뮤니티에 의견이나 의견을 보내주십시오. 감사합니다.이 안내서가 도움이 되었기를 바랍니다.
등록된 댓글이 없습니다.