Создание приборной панели Apache ECharts с помощью React и Cube

Этот гостевой пост написал Осиначи Чуквуджама. Он является веб-разработчиком и техническим писателем. Ему нравится создавать внутренние приложения и использовать облачные вычисления. В свободное от кодинга время он играет на органе и создает случайные анимации. Вы можете связаться с ним через его веб-сайт.


В мире, где каждая организация имеет большие данные, упрощенный подход к анализу данных как никогда востребован. К счастью, библиотеки построения диаграмм с открытым исходным кодом, такие как Chart.js, Recharts и Apache ECharts, достаточно надежны для анализа больших данных. Такие инструменты, как Power BI, Snowflake и Cube, также помогают упростить аналитику, облегчая организациям использование данных для принятия решений.

В этой статье вы узнаете, как использовать Apache ECharts, Cube и React для создания аналитической панели электронной коммерции.

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

Результат должен выглядеть следующим образом:

Вы можете найти живую демонстрацию здесь или просмотреть исходный код приложения React в этом репозитории GitHub.

Что такое Apache ECharts?

Apache ECharts — это надежная библиотека построения графиков на JavaScript. Она полностью упакована и предлагает такие распространенные типы диаграмм, как линия, столбец и круговая, а также более сложные типы диаграмм, такие как graph, themeRiver и gauge.

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

Он также невероятно настраивается, позволяя изменять цвет и размер в соответствии с потребностями вашего приложения. Однако рендеринг происходит на стороне клиента. Поэтому если устройство, на котором отображается график, имеет мало памяти, визуализация будет происходить медленнее. Рендеринг происходит быстрее, если вы используете Google Charts, но все ваши данные не хранятся на вашем собственном сервере, как это происходит в ECharts, а значит, их может просматривать Google или любая другая третья сторона.

В отличие от Recharts, ECharts — это в первую очередь библиотека JavaScript. Это означает, что вы не получите компоненты React для осей, легенд и других частей графика. Вместо этого вы будете использовать объект для декларативного определения рендеринга и поведения графика.

Зачем интегрироваться с Cube?

ECharts легко интегрируется с Cube, предлагая превосходные визуализации для данных, возвращаемых API Cube. Все, что вам нужно — это ваши данные, несколько запросов и передача полученных данных API через диаграмму ECharts.

Реализация приборной панели ECharts с помощью React и Cube

Следующий пример проекта состоит из трех основных компонентов:

  • Реляционная база данных (PostgresSQL в этом учебнике, но вы можете использовать MySQL, MongoDB или любую другую базу данных, поддерживаемую Cube).
  • схема Cube
  • Настройка ECharts в React

Чтобы следовать этому примеру, у вас должен быть установлен Docker.

Настройка Cube

Чтобы установить Cube с помощью Docker, измените каталог на нужное место и выполните следующую команду:

docker run -p 4000:4000 
  -v ${PWD}:/cube/conf 
  -e CUBEJS_DEV_MODE=true 
  cubejs/cube
Войти в полноэкранный режим Выйти из полноэкранного режима

Эта команда загружает образ Cube Docker и открывает порт 4000 для доступа к игровой площадке Cube. Вы можете перейти по адресу http://localhost:4000 в браузере, чтобы увидеть игровую площадку.

Отсюда вы должны выбрать тип базы данных и параметры В этой статье будет использоваться размещенная база данных Postgres, предлагаемая Cube. Выберите Postgres в качестве типа базы данных и используйте приведенные ниже параметры, чтобы завершить настройку экземпляра Cube:

Имя хоста: demo-db.cube.dev

База данных: ecom

Имя пользователя: cube

Пароль: 12345

Генерация схемы данных с помощью Cube

Cube поставляется с конструктором схем, который позволяет создавать желаемые запросы. Эти запросы помогут вам задать аналитические вопросы о ваших данных, например, такие:

  • Сколько заказов было сделано в этом месяце?
  • Каково общее количество проданных товаров?

Чтобы продолжить, выберите все таблицы в публичной схеме на вкладке Схема на игровой площадке Куб.

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

Нажмите на кнопку Построить.

Обзор игровой площадки Cube Playground

Cube Playground состоит из трех вкладок.

  1. Вкладка Build — для построения графиков на основе схемы данных.
  2. Вкладка Dashboard App для просмотра графиков, созданных на вкладке Build
  3. Вкладка Schema для выбора таблиц, данные из которых будут использоваться для построения графиков.

Схема, созданная Cube, представляет собой объект JavaScript, состоящий из мер и измерений. Он используется для генерации SQL-кода, который будет запрашиваться в базе данных для аналитики.

В приведенном ниже фрагменте кода показана схема данных для таблицы users. Она содержит меру count и три измерения, которые соответствуют столбцам таблицы users:

cube(`Users`, {
  sql: `SELECT * FROM users`,

  measures: {
    count: {
      sql: `id`,
      type: `count`,
    },
  },

  dimensions: {
    city: {
      sql: `city`,
      type: `string`,
    },

    signedUp: {
      sql: `created_at`,
      type: `time`,
    },

    companyName: {
      sql: `company_name`,
      type: `string`,
    },
  },
});
Войти в полноэкранный режим Выход из полноэкранного режима

Cube позволяет комбинировать меры и измерения, чтобы задавать вопросы типа «На какие компании работают наши пользователи?»:

{
   measures: ['Users.count'],
   dimensions: ['Users.companyName']
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Или «Где работают наши пользователи?»:

{
   measures: ['Users.count'],
   dimensions: ['Users.city']
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Настройка ECharts в проекте React

Чтобы настроить ECharts в проекте React, создайте новый проект React в нужной вам директории и запустите dev-сервер с помощью команды ниже.

npx create-react-app cube-echarts-app
cd cube-echarts-app
npm start
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь установите необходимые зависимости:

npm i --save @cubejs-client/core @cubejs-client/react echarts echarts-for-react react-loader-spinner dayjs react-bootstrap bootstrap
Войти в полноэкранный режим Выйти из полноэкранного режима

Базовая установка приложения

Теперь, когда зависимости установлены, создайте папку components с помощью этой команды:

mkdir src/components
Войти в полноэкранный режим Выйти из полноэкранного режима

Замените содержимое файла App.js на следующее:

import React from "react";
import { CubeProvider } from "@cubejs-client/react";
import cubejs from "@cubejs-client/core";
import { Navbar, Container, Row, Col } from "react-bootstrap";

export const cubejsApi = cubejs(process.env.REACT_APP_CUBEJS_TOKEN,
  { apiUrl: "http://localhost:4000/cubejs-api/v1" }
);

const App = () => {
  return (
    <CubeProvider cubejsApi={cubejsApi}>
      <div className="bg-gray">
        <Navbar>
          <Container>
            <Navbar.Brand href="#home">E-Commerce Dashboard</Navbar.Brand>
          </Container>
        </Navbar>
      </div>
    </CubeProvider>
  );
};

export default App;
Войти в полноэкранный режим Выйти из полноэкранного режима

Вам не нужно устанавливать REACT_APP_CUBEJS_TOKEN в вашей среде разработки, так как он строго используется в продакшене. Если вы хотите установить его, вам нужно подписать JWT на https://jwt.io или с помощью вашего любимого инструмента с CUBEJS_API_SECRET в качестве секретного ключа. Вы можете найти CUBEJS_API_SECRET в .env файле настройки бэкэнда Cube, который автоматически создается Cube.

Приборная панель будет содержать четыре графика:

  1. Диаграмма области, содержащая рост выручки за предыдущий год
  2. Линейная диаграмма, содержащая заказы за последние тридцать дней
  3. Сложенная гистограмма, содержащая заказы по статусу с течением времени
  4. гистограмма, содержащая заказы по названию категории продукта

Чтобы приступить к созданию этих графиков, создайте необходимые файлы графиков и загрузчик:

touch src/components/AreaChart.jsx
touch src/components/BarChart.jsx
touch src/components/LineChart.jsx
touch src/components/StackedBarChart.jsx
touch src/components/Loader.jsx
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Добавьте следующее в Loader.jsx:

import { Oval } from "react-loader-spinner";

function Loader() {
  return (
    <div className="d-flex justify-content-center align-items-center">
      <Oval heigth="100" width="100" color="#5470C6" ariaLabel="loading" />
    </div>
  );
}

export default Loader;
Войти в полноэкранный режим Выйти из полноэкранного режима

Откройте AreaChart.jsx и добавьте следующее:

import React from "react";
import ReactECharts from "echarts-for-react";
import { useCubeQuery } from "@cubejs-client/react";
import Loader from "./Loader";
import { Card } from "react-bootstrap";
import dayjs from "dayjs";

function AreaChart() {
  const { resultSet, isLoading, error, progress } = useCubeQuery({
    measures: ["Users.count"],
    timeDimensions: [
      {
        dimension: "Users.createdAt",
        granularity: "year",
      },
    ],
    order: {
      "Users.createdAt": "asc",
    },
  });

  if (error) {
    return <p>{error.toString()}</p>;
  }
  if (isLoading) {
    return (
      <div>
        {(progress && progress.stage && progress.stage.stage) || <Loader />}
      </div>
    );
  }

  if (!resultSet) {
    return null;
  }

  const workingData = resultSet.loadResponse.results[0].data;
  const userCount = workingData.map((item) => item["Users.count"]);
  const userCreationDate = workingData.map((item) =>
    dayjs(item["Users.createdAt.year"]).format("YYYY")
  );

  const options = {
    legend: {
      data: ["User count"],
    },
    tooltip: {
      trigger: "axis",
      axisPointer: {
        type: "shadow",
      },
    },
    xAxis: {
      data: userCreationDate,
    },
    yAxis: {},
    series: [
      {
        name: "User count",
        data: userCount,
        type: "line",
        areaStyle: {},
      },
    ],
  };

  return (
    <Card className="m-4">
      <Card.Body>
        <Card.Title>User Trend</Card.Title>
        <ReactECharts option={options} />
      </Card.Body>
    </Card>
  );
}

export default AreaChart;
Войти в полноэкранный режим Выйти из полноэкранного режима

Первая часть файла содержит хук React, который получает данные из бэкэнда Cube, как показано ниже:

const { resultSet, isLoading, error, progress } = useCubeQuery({
  measures: ["Users.count"],
  timeDimensions: [
    {
      dimension: "Users.createdAt",
      granularity: "year",
    },
  ],
  order: {
    "Users.createdAt": "asc",
  },
});
Войти в полноэкранный режим Выход из полноэкранного режима

Объект в этом хуке может быть получен с игровой площадки Cube в виде JSON-запроса.

Во второй части AreaChart.jsx показано, как возвращаемое состояние используется для условного рендеринга:

if (error) {
  return <p>{error.toString()}</p>;
}
if (isLoading) {
  return (
    <div>
      {(progress && progress.stage && progress.stage.stage) || <Loader />}
    </div>
  );
}

if (!resultSet) {
  return null;
}
Войти в полноэкранный режим Выход из полноэкранного режима

Третья часть AreaChart.jsx преобразует возвращаемые данные в форму, которую может отобразить график. Диаграмма отвечает на вопрос «Сколько пользователей присоединялось каждый год?», а userCount и userCreationDate будут выделены из возвращаемых данных:

const workingData = resultSet.loadResponse.results[0].data;
const userCount = workingData.map((item) => item["Users.count"]);
const userCreationDate = workingData.map((item) =>
  dayjs(item["Users.createdAt.year"]).format("YYYY")
);
Вход в полноэкранный режим Выход из полноэкранного режима

Наконец, данные графика и метаданные объявляются в объекте options и передаются компоненту ReactECharts:

const options = {
  legend: {
    data: ["User count"],
  },
  tooltip: {
    trigger: "axis",
    axisPointer: {
      type: "shadow",
    },
  },
  xAxis: {
    data: userCreationDate,
  },
  yAxis: {},
  series: [
    {
      name: "User count",
      data: userCount,
      type: "line",
      areaStyle: {},
    },
  ],
};

return (
  <Card className="m-4">
    <Card.Body>
      <Card.Title>User Trend</Card.Title>
      <ReactECharts option={options} />
    </Card.Body>
  </Card>
);
Войти в полноэкранный режим Выход из полноэкранного режима

Чтобы увидеть график в браузере, обновите содержимое App.js, чтобы включить его.

+ import AreaChart from "./components/AreaChart";

const App = () => {
...
<div className="bg-gray">
  <Navbar>
    <Container>
      <Navbar.Brand href="#home">E-Commerce Dashboard</Navbar.Brand>
    </Container>
  </Navbar>

+  <Row>
+   <Col>
+     <AreaChart />
+    </Col>
+  </Row>

</div>
...
Войти в полноэкранный режим Выйти из полноэкранного режима

Добавьте следующее содержимое в остальные файлы, как указано ниже.

LineChart.jsx:

import React from "react";
import ReactECharts from "echarts-for-react";
import { useCubeQuery } from "@cubejs-client/react";
import Loader from "./Loader";
import { Card } from "react-bootstrap";

function LineChart() {
  const { resultSet, isLoading, error, progress } = useCubeQuery({
    measures: ["Products.count"],
    order: [["Products.count", "asc"]],
    dimensions: ["ProductCategories.name"],
  });

  if (error) {
    return <p>{error.toString()}</p>;
  }
  if (isLoading) {
    return (
      <div>
        {(progress && progress.stage && progress.stage.stage) || <Loader />}
      </div>
    );
  }

  if (!resultSet) {
    return null;
  }

  const workingData = resultSet.loadResponse.results[0].data;
  const productCategoryNames = workingData.map(
    (item) => item["ProductCategories.name"]
  );
  const productCategoriesCount = workingData.map(
    (item) => item["Products.count"]
  );

  const options = {
    legend: {
      data: ["Product Categories count"],
    },
    tooltip: {
      trigger: "axis",
      axisPointer: {
        type: "shadow",
      },
    },
    xAxis: {
      data: productCategoryNames,
    },
    yAxis: {},
    series: [
      {
        name: "Product Categories count",
        data: productCategoriesCount,
        type: "line",
      },
    ],
  };

  return (
    <Card className="m-4">
      <Card.Body>
        <Card.Title>Products by Category</Card.Title>
        <ReactECharts option={options} />
      </Card.Body>
    </Card>
  );
}

export default LineChart;
Войти в полноэкранный режим Выйти из полноэкранного режима

StackedBarChart.jsx:

import React from "react";
import ReactECharts from "echarts-for-react";
import { useCubeQuery } from "@cubejs-client/react";
import dayjs from "dayjs";
import Loader from "./Loader";
import { Card } from "react-bootstrap";

function StackedBarChart() {
  const { resultSet, isLoading, error, progress } = useCubeQuery({
    measures: ["Orders.count"],
    timeDimensions: [
      {
        dimension: "Orders.createdAt",
        granularity: "month",
      },
    ],
    order: [
      ["Orders.count", "desc"],
      ["Orders.createdAt", "asc"],
    ],
    dimensions: ["Orders.status"],
    filters: [],
  });

  if (error) {
    return <p>{error.toString()}</p>;
  }
  if (isLoading) {
    return (
      <div>
        {(progress && progress.stage && progress.stage.stage) || <Loader />}
      </div>
    );
  }

  if (!resultSet) {
    return null;
  }

  const returnedData = resultSet.loadResponse.results[0].data.sort(
    (first, second) =>
      dayjs(first["Orders.createdAt.month"]).diff(
        dayjs(second["Orders.createdAt.month"])
      )
  );

  const filterOrderStatusBy = (type) =>
    returnedData
      .filter((order) => order["Orders.status"] === type)
      .map((order) => order["Orders.count"]);

  const ordersProcessing = filterOrderStatusBy("processing");
  const ordersCompleted = filterOrderStatusBy("completed");
  const ordersShipped = filterOrderStatusBy("shipped");

  const orderMonths = [
    ...new Set(
      returnedData.map((order) => {
        return dayjs(order["Orders.createdAt.month"]).format("MMM YYYY");
      })
    ),
  ];

  const options = {
    legend: {
      data: [
        "Processing Orders count",
        "Completed Orders count",
        "Shipped Orders count",
      ],
    },
    tooltip: {
      trigger: "axis",
      axisPointer: {
        type: "shadow",
      },
    },
    xAxis: {
      data: orderMonths,
    },
    yAxis: {},
    series: [
      {
        name: "Processing Orders count",
        data: ordersProcessing,
        type: "bar",
        stack: "x",
      },
      {
        name: "Completed Orders count",
        data: ordersCompleted,
        type: "bar",
        stack: "x",
      },
      {
        name: "Shipped Orders count",
        data: ordersShipped,
        type: "bar",
        stack: "x",
      },
    ],
  };

  return (
    <Card className="m-4">
      <Card.Body>
        <Card.Title>Orders by Status Over Time</Card.Title>
        <ReactECharts option={options} />
      </Card.Body>
    </Card>
  );
}

export default StackedBarChart;
Войти в полноэкранный режим Выход из полноэкранного режима

BarChart.jsx:

import React from "react";
import ReactECharts from "echarts-for-react";
import { useCubeQuery } from "@cubejs-client/react";
import Loader from "./Loader";
import { Card } from "react-bootstrap";

function BarChart() {
  const { resultSet, isLoading, error, progress } = useCubeQuery({
    measures: ["Orders.count"],
    timeDimensions: [],
    order: {
      "Orders.count": "desc",
    },
    dimensions: ["ProductCategories.name"],
  });

  if (error) {
    return <p>{error.toString()}</p>;
  }
  if (isLoading) {
    return (
      <div>
        {(progress && progress.stage && progress.stage.stage) || <Loader />}
      </div>
    );
  }

  if (!resultSet) {
    return null;
  }

  const workingData = resultSet.loadResponse.results[0].data;
  const productCategoryNames = workingData.map(
    (item) => item["ProductCategories.name"]
  );
  const orderCount = workingData.map((item) => item["Orders.count"]);

  const options = {
    xAxis: {
      type: "category",
      data: productCategoryNames,
    },
    yAxis: {
      type: "value",
    },
    series: [
      {
        data: orderCount,
        type: "bar",
      },
    ],
  };

  return (
    <Card className="m-4">
      <Card.Body>
        <Card.Title>Orders by Product Category Names</Card.Title>
        <ReactECharts option={options} />
      </Card.Body>
    </Card>
  );
}

export default BarChart;
Войти в полноэкранный режим Выход из полноэкранного режима

Наконец, обновите App.js, чтобы включить новые графики:

+ import LineChart from "./components/LineChart";
+ import StackedBarChart from "./components/StackedBarChart";
+ import BarChart from "./components/AreaChart";

const App = () => {
...
<div className="bg-gray">
  <Navbar>
    <Container>
      <Navbar.Brand href="#home">E-Commerce Dashboard</Navbar.Brand>
    </Container>
  </Navbar>

   <Row>
    <Col>
      <AreaChart />
     </Col>
+   <Col>
+     <LineChart />
+   </Col>
+ </Row>
+ <StackedBarChart />
+ <BarChart />
</div>
Войти в полноэкранный режим Выйти из полноэкранного режима

Добавление интерактивности в приборную панель

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

Перейдите к компоненту StackedBarChart.jsx и импортируйте следующее:

import { useState } from "react";
import { Card, Form, Button } from "react-bootstrap";
Вход в полноэкранный режим Выход из полноэкранного режима

Затем определите начальную дату, конечную дату и крючки запроса JSON:

const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");

const [jsonQuery, setJSONQuery] = useState({
  measures: ["Orders.count"],
  timeDimensions: [
    {
      dimension: "Orders.createdAt",
      granularity: "month",
    },
  ],
  order: [
    ["Orders.count", "desc"],
    ["Orders.createdAt", "asc"],
  ],
  dimensions: ["Orders.status"],
  filters: [],
});
const { resultSet, isLoading, error, progress } = useCubeQuery(jsonQuery);
Войти в полноэкранный режим Выйти из полноэкранного режима

После этого добавьте функцию, которая будет обрабатывать обновление даты:

const updateDate = (event) => {
  event.preventDefault();

  setJSONQuery((prevJSONQuery) => {
    return {
      ...prevJSONQuery,
      filters: [
        {
          member: "Orders.createdAt",
          operator: "inDateRange",
          values: [startDate, endDate],
        },
      ],
    };
  });
};
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем обновите содержимое Card.Body следующим образом:

<Card.Body>
  <div className="d-flex align-items-center justify-content-between my-4">
    <Card.Title>Orders by Status Over Time</Card.Title>
    <Form onSubmit={updateDate} className="d-flex align-items-center  gap-4">
      <div className="d-flex gap-2 align-items-center">
        <div>
          <label htmlFor="startDate">Start Date</label>
        </div>

        <input
          id="startDate"
          name="start-date"
          value={startDate}
          onChange={({ target }) => setStartDate(target.value)}
          type="date"
        />
      </div>
      <div className="d-flex gap-2 align-items-center">
        <div>
          <label htmlFor="endDate">End Date</label>
        </div>
        <input
          id="endDate"
          name="end-date"
          value={endDate}
          onChange={({ target }) => setEndDate(target.value)}
          type="date"
        />
      </div>
      <Button type="submit">Set date</Button>
    </Form>
  </div>

  <ReactECharts option={options} />
</Card.Body>
Войти в полноэкранный режим Выйти из полноэкранного режима

Если вы посмотрите на свой график в браузере, вы должны увидеть форму даты на графике и иметь возможность самостоятельно обновить дату. На видео ниже показана демонстрация фильтра даты:

Ознакомьтесь с этим руководством по D3 или этим руководством по Material UI, чтобы узнать больше о добавлении интерактивности в ваши графики.

Заключение

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

Cube — это безголовый слой API, который соединяет вашу базу данных через любой из трех API, включая REST, GraphQL и SQL, с вашим внешним кодом, чтобы вы могли быстрее создавать приложения для работы с данными. Это упрощает процесс добавления аналитических элементов в существующие приложения. С помощью Cube вы можете создать уровень API, управлять контролем доступа, агрегировать данные, кэшировать запросы для повышения производительности и легко интегрировать Apache ECharts.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *