Tutoriel de tableau de bord D3 avec Cube.js
Dans ce didacticiel, je vais couvrir la création d’une application de tableau de bord de base avec Cube.js et la bibliothèque la plus populaire pour visualiser les données – D3.js. Bien que Cube.js ne fournisse pas lui-même de couche de visualisation, il est très facile de l’intégrer à n’importe quelle bibliothèque de graphiques existante. De plus, vous pouvez utiliser Modèles Cube.js pour échafauder une application frontale avec votre bibliothèque de graphiques, votre infrastructure frontale et votre kit d’interface utilisateur préférés. Le moteur d’échafaudage va tout câbler et le configurer pour qu’il fonctionne avec le backend Cube.js.
Tu peux vérifier la démo en ligne de ce tableau de bord ici et le code source complet de l’exemple d’application est disponible sur Github.
Nous allons utiliser Postgres pour stocker nos données. Cube.js s’y connectera et agira comme un middleware entre la base de données et le client, fournissant l’API, l’abstraction, la mise en cache et bien plus encore. Sur le frontend, nous aurons React with Material UI et D3 pour le rendu des graphiques. Ci-dessous, vous pouvez trouver un schéma de toute l’architecture de l’exemple d’application.
Si vous avez des questions en parcourant ce guide, n’hésitez pas à vous joindre à ce Communauté Slack et postez votre question là-bas.
Happy Hacking! 💻
La première chose que nous devons mettre en place est une base de données. Nous utiliserons Postgres pour ce didacticiel. Cependant, vous pouvez utiliser votre base de données SQL (ou Mongo) préférée. Veuillez vous référer au Documentation de Cube.js sur la façon de se connecter à différentes bases de données.
Si vous ne disposez d’aucune donnée pour le tableau de bord, vous pouvez charger notre exemple de jeu de données Post-commerce électronique.
$ curl http://cube.dev/downloads/ecom-dump-d3-example.sql > ecom-dump.sql
$ createdb ecom
$ psql --dbname ecom -f ecom-dump.sql
Maintenant que nous avons des données dans la base de données, nous sommes prêts à créer le service backend Cube.js. Exécutez les commandes suivantes dans votre terminal:
$ npm install -g cubejs-cli
$ cubejs create d3-dashboard -d postgres
Les commandes ci-dessus installent Cube.js CLI et créent un nouveau service, configuré pour fonctionner avec une base de données Postgres.
Cube.js utilise des variables d’environnement pour la configuration. Il utilise des variables d’environnement commençant par CUBEJS_
. Pour configurer la connexion à notre base de données, nous devons spécifier le type et le nom de la base de données. Dans le dossier du projet Cube.js, remplacez le contenu de .env par ce qui suit:
CUBEJS_API_SECRET=SECRET
CUBEJS_DB_TYPE=postgres
CUBEJS_DB_NAME=ecom
CUBEJS_WEB_SOCKETS=true
Maintenant, commençons le serveur et ouvrons le terrain de jeux pour développeurs à http: // localhost: 4000.
$ npm run dev
L’étape suivante consiste à créer un Schéma de données Cube.js. Cube.js utilise le schéma de données pour générer un code SQL, qui sera exécuté dans votre base de données. Cube.js Playground peut générer des schémas simples basés sur les tables de la base de données. Accédez à la page Schéma et générons les schémas dont nous avons besoin pour notre tableau de bord. Sélectionnez le line_items
, orders
, products
, product_categories
, et users
tableaux et cliquez Générer un schéma.
Testons notre nouveau schéma généré. Accédez à la page Créer et sélectionnez une mesure dans la liste déroulante. Vous devriez pouvoir voir un graphique linéaire simple. Vous pouvez choisir D3 dans la liste déroulante de la bibliothèque de graphiques pour voir un exemple de visualisation D3. Notez que ce n’est qu’un exemple et que vous pouvez toujours le personnaliser et le développer.
Maintenant, faisons quelques mises à jour de notre schéma. La génération de schéma facilite le démarrage et le test de l’ensemble de données, mais pour les cas d’utilisation réels, nous devons presque toujours effectuer des modifications manuelles.
Dans le schéma, nous définissons des mesures et des dimensions et comment elles sont mappées dans des requêtes SQL. Vous pouvez trouver une documentation complète sur schéma de données ici. Nous allons ajouter un priceRange
dimension au cube Commandes. Il indiquera si le prix total de la commande tombe dans l’un des compartiments: «0 $ – 100 $», «100 $ – 200 $», «200 $ +».
Pour ce faire, nous devons d’abord définir un price
dimension de la commande. Dans notre base de données, orders
n’ont pas de colonne de prix, mais nous pouvons la calculer sur la base du prix total du line_items
à l’intérieur de la commande. Notre schéma a déjà automatiquement indiqué et défini une relation entre le Orders
et LineTimes
cubes. Vous pouvez en savoir plus sur rejoint ici.
// You can check the belongsTo join
// to the Orders cube inside the LineItems cube
joins: {
Orders: {
sql: `${CUBE}.order_id = ${Orders}.id`,
relationship: `belongsTo`
}
}
le LineItems
cube a price
mesurer avec un sum
type. Nous pouvons référencer cette mesure à partir du Orders
cube en tant que dimension et il nous donnera la somme de tous les éléments de campagne qui appartiennent à cet ordre. Cela s’appelle un subQuery
dimension; vous pouvez en savoir plus ici.
// Add the following dimension to the Orders cube
price: {
sql: `${LineItems.price}`,
subQuery: true,
type: `number`,
format: `currency`
}
Maintenant, sur la base de cette dimension, nous pouvons créer un priceRange
dimension. Nous utiliserons un déclaration de cas pour définir une logique conditionnelle pour nos catégories de prix.
// 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+`
}
}
}
Essayons notre dimension nouvellement créée! Accédez à la page Construire dans la cour de récréation, sélectionnez la mesure Nombre de commandes avec la dimension de la fourchette de prix Commandes. Vous pouvez toujours vérifier le SQL généré en cliquant sur le SQL sur la barre de commande.
C’est tout pour le backend! Dans la partie suivante, nous verrons de plus près comment rendre les résultats de nos requêtes avec D3.
Maintenant que nous pouvons créer notre premier graphique, examinons l’exemple de code utilisé par la cour de récréation pour le rendre avec le D3. Avant cela, nous devons comprendre comment Cube.js accepte et traite une requête et renvoie le résultat.
Une requête Cube.js est un simple objet JSON contenant plusieurs propriétés. Les principales propriétés de la requête sont measures
, dimensions
, timeDimensions
, et filters
. Vous pouvez en savoir plus sur le Format de requête JSON Cube.js et ses propriétés ici. Vous pouvez toujours inspecter la requête JSON dans la cour de récréation en cliquant sur le Requête JSON à côté du sélecteur de graphique.
Le backend Cube.js accepte cette requête, puis l’utilise ainsi que le schéma que nous avons créé précédemment pour générer une requête SQL. Cette requête SQL sera exécutée dans notre base de données et le résultat sera renvoyé au client.
Bien que Cube.js puisse être interrogé via une API HTTP REST simple, nous allons utiliser la bibliothèque client JavaScript Cube.js. Il fournit entre autres des outils utiles pour traiter les données après leur retour par le backend.
Une fois les données chargées, le client Cube.js crée un ResultSet
objet, qui fournit un ensemble de méthodes pour accéder et manipuler les données. Nous allons en utiliser deux maintenant: ResultSet.series
et ResultSet.chartPivot
. Vous pouvez en apprendre davantage sur toutes les fonctionnalités du Bibliothèque cliente Cube.js dans la documentation.
le ResultSet.series
retourne un tableau de séries de données avec des données de clé, de titre et de série. La méthode accepte un argument:pivotConfig
. C’est un objet, contenant des règles sur la façon dont les données doivent être pivotées; nous en parlerons un peu. Dans un graphique linéaire, chaque série est généralement représentée par une ligne distincte. Cette méthode est utile pour préparer les données dans le format attendu par 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 },
//...
]
}
]
La prochaine méthode dont nous avons besoin est ResultSet.chartPivot
. Il accepte le même pivotConfig
et renvoie un tableau de données avec des valeurs pour l’axe X et pour chaque série que nous avons.
// 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 },
//...
]
Comme mentionné ci-dessus, le pivotConfig
L’argument est un objet permettant de contrôler comment transformer ou faire pivoter des données. L’objet a deux propriétés: x
et y
, les deux sont des tableaux. En ajoutant des mesures ou des dimensions à l’une d’entre elles, vous pouvez contrôler ce qui va sur l’axe X et ce qui va sur l’axe Y. Pour une requête avec un measure
et une timeDimension
, pivotConfig
a la valeur par défaut suivante:
{
x: `CubeName.myTimeDimension.granularity`,
y: `measures`
}
Ici, «mesures» est une valeur spéciale, ce qui signifie que toutes les mesures doivent aller sur l’axe Y. Dans la plupart des cas, la valeur par défaut de pivotConfig
devrait bien fonctionner. Dans la partie suivante, je vais vous montrer quand et comment nous devons le changer.
Maintenant, regardons le terrain de jeu de code frontal généré lorsque nous sélectionnons un graphique D3. Sélectionnez une mesure dans le terrain de jeu et changez le type de visualisation en D3. Ensuite, cliquez sur le Code pour inspecter le code frontal pour rendre le graphique.
Voici le code source complet de cette page.
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 }) => (el && draw(el, resultSet, 'line')} />
)
const API_URL = "http://localhost:4000"; // change to your actual endpointconst cubejsApi = cubejs(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1NzkwMjU0ODcsImV4cCI6MTU3OTExMTg4N30.nUyJ4AEsNk9ks9C8OwGPCHrcTXyJtqJxm02df7RGnQU",
{ apiUrl: API_URL + "/cubejs-api/v1" }
);const renderChart = (Component) => ({ resultSet, error }) => (
(resultSet &&) || const ChartRenderer = () =>
(error && error.toString()) ||
()
)query={{ export default ChartRenderer;
"measures": [
"Orders.count"
],
"timeDimensions": [
{
"dimension": "Orders.createdAt",
"granularity": "month"
}
],
"filters": []
}}
cubejsApi={cubejsApi}
render={renderChart(lineRender)}
/>;
Le composant React qui rend le graphique n’est qu’une seule ligne enveloppant un draw
fonction, qui fait tout le travail.
const lineRender = ({ resultSet }) => (
el && draw(el, resultSet, 'line')} />
)
Il se passe beaucoup de choses dans ce draw
une fonction. Bien qu’il affiche déjà un graphique, considérez-le comme un exemple et un bon point de départ pour la personnalisation. Comme nous travaillerons sur notre propre tableau de bord dans la partie suivante, je vais vous montrer comment le faire.
N’hésitez pas à cliquer sur le Éditer et jouez avec le code dans Code Sandbox.
Nous sommes maintenant prêts à créer notre application frontale. Nous allons utiliser les modèles Cube.js, qui est un moteur d’échafaudage pour créer rapidement des applications frontales configurées pour fonctionner avec le backend Cube.js. Il fournit une sélection de différents cadres frontaux, kits d’interface utilisateur et bibliothèques de graphiques à mélanger. Nous choisirons React, Material UI et D3.js. Naviguons vers l’onglet Dashboard App et créons une nouvelle application de tableau de bord.
La génération d’une application et l’installation de toutes les dépendances peuvent prendre plusieurs minutes. Une fois cela fait, vous aurez un dashboard-app
dans votre dossier de projet Cube.js. Pour démarrer une application frontale, allez dans l’onglet «Dashboard App» dans la cour de récréation et appuyez sur le bouton «Démarrer», ou exécutez la commande suivante dans le dossier dashboard-app:
$ npm start
Assurez-vous que le processus d’arrière-plan Cube.js est opérationnel et que notre application frontale utilise son API. L’application frontale s’exécute sur http: // localhost: 3000. Si vous l’ouvrez dans votre navigateur, vous devriez pouvoir voir un tableau de bord vide.
Pour ajouter un graphique au tableau de bord, nous pouvons soit le créer dans la cour de récréation et cliquer sur le bouton «ajouter au tableau de bord», soit modifier le src/pages/DashboardPage.js
fichier dans le dashboard-app
dossier. Allons-y avec cette dernière option. Entre autres choses, ce fichier déclare la DashboardItems
variable, qui est un tableau de requêtes pour les graphiques.
Éditer dashboard-app/src/pages/DashboardPage.js
pour ajouter des graphiques au tableau de bord.
-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"
+ }
+ }
+];
Comme vous pouvez le voir ci-dessus, nous venons d’ajouter un tableau d’objets de requête Cube.js.
Si vous actualisez le tableau de bord, vous devriez pouvoir voir vos graphiques!
Vous pouvez remarquer qu’une de nos requêtes a le pivotConfig
défini comme suit.
pivotConfig: {
x: ["Users.city"],
y: ["Orders.status", "measures"]
}
Comme je l’ai mentionné dans la partie précédente, la valeur par défaut du pivotConfig
fonctionne généralement bien, mais dans certains cas comme celui-ci, nous devons l’ajuster pour obtenir le résultat souhaité. Nous voulons tracer ici un graphique à barres avec les villes sur l’axe X et le nombre de commandes sur l’axe Y regroupées par les statuts des commandes. C’est exactement ce que nous passons ici dans le pivotConfig
: Users.city
à l’axe X et mesure avec Orders.status
à l’axe Y pour obtenir le résultat groupé.
Pour personnaliser le rendu des graphiques, vous pouvez modifier le dashboard-app/src/pages/ChartRenderer.js
fichier. Cela devrait ressembler à ce que nous avons vu dans la partie précédente.
Tu peux vérifier la démo en ligne de ce tableau de bord ici et le code source complet de l’exemple d’application est disponible sur Github.
Félicitations pour avoir terminé ce guide! 🎉
Je serais ravi de vous entendre au sujet de votre expérience en suivant ce guide. Veuillez envoyer tout commentaire ou commentaire que vous pourriez avoir ici dans les commentaires ou dans ce Communauté Slack. Merci et j’espère que vous avez trouvé ce guide utile!