Визуализация данных - мощное средство визуальной передачи больших объемов информации. Когда дело доходит до наблюдаемости, это также одна из основных функций Datadog (службы мониторинга облачных приложений). Выбор правильной визуализации для ваших данных - важная часть предоставления удобочитаемых представлений о состоянии и производительности ваших систем.

📈 Datadog + визуализация данных

Дизайнерам Datadog требуется нечто большее, чем просто статические диаграммы и красивые визуализации; им нужно идти дальше, углубляясь в детали, исследуя дальше и понимая силы, влияющие на их конструкции. В настоящее время мы используем набор графиков, доступных в Figma, для создания статических визуализаций и передачи их сообщений заинтересованным сторонам. Они хорошо работают в случае разработки пользовательского интерфейса, не зависящего от визуализации.

Но что, если вы хотите разработать новое взаимодействие между графиком и элементом пользовательского интерфейса в вашем дизайне? Вот тогда и пригодится создание прототипа с помощью Framer X.

Framer X - единственный инструмент дизайна, который позволяет создавать компоненты кода, которые можно использовать внутри инструмента дизайна вместе с другими компонентами дизайна и кода.

Вот краткий обзор того, как мы используем Framer X в Datadog для имитации взаимодействия между визуализацией и набором данных:

Следующая часть статьи состоит из двух частей - компонент «Диаграмма» и «Диаграмма + пользовательский интерфейс». Важно понимать, как работает этот компонент, чтобы настроить его под свои нужды и максимально использовать его, поскольку возможности безграничны.

📊 Компонент диаграммы

Почему?

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

Как?

В этом руководстве предполагается, что у вас есть базовая среда разработки с использованием Node, Yarn и Git, а также базовые знания Javascript и React.

Первым делом нужно запустить Framer X.

Чтобы получить диаграммы во Framer X, мы будем использовать библиотеку диаграмм под названием ApexCharts.js, которая имеет интеграцию с React, которая может хорошо работать в данной среде. Давайте установим эту и другие зависимости сначала в папку проекта:

Откройте эту папку в редакторе кода (например, Visual Studio Code) и выполните в терминале следующую команду:

yarn add apexcharts react-apexcharts

Теперь создадим компонент в папке code/. Назовем его Charting.tsx и вставим следующий код:

Затем мы настраиваем свойства компонента, а также некоторые значения по умолчанию:

Мы будем использовать крошечную функцию, чтобы сгенерировать фиктивные данные для наших диаграмм:

Большинство свойств, которые мы только что определили, будут переданы как параметры компоненту ApexChart. Но для сохранения состояния нам нужно сделать две реактивные вещи - и для этого мы можем использовать React Hooks:

import { addPropertyControls, Color, ControlType, Frame } from "framer";
import * as React from "react";
import Chart from "react-apexcharts";
enum ChartTypes {
  Line = "line",
  Area = "area",
  Bar = "bar",
  Heatmap = "heatmap",
  Radar = "radar"
}
export interface Props {
  width?: number;
  height?: number;
  points: number;
  seriesCount: number;
  chartType: ChartTypes;
  showToolbar: boolean;
  showLegend: boolean;
  showXaxis: boolean;
  showYaxis: boolean;
  isStacked: boolean;
  isHorizontal: boolean;
  colors: string[];
  palette?: string;
  showStroke: boolean;
  stroke: number;
  strokeCurve: "smooth" | "straight" | "stepline";
  fillType: "solid" | "gradient" | "pattern";
  fillOpacity: number;
  onDataPointSelection?: () => void;
  onDataPointMouseEnter?: () => void;
  onDataPointMouseLeave?: () => void;
}
export const Charting = function(props) {
  const {
    width,
    height,
    chartType,
    isHorizontal,
    points,
    seriesCount,
    fillType,
    fillOpacity,
    showStroke,
    stroke,
    strokeCurve,
    palette,
    showToolbar,
    showLegend,
    showXaxis,
    showYaxis,
    isStacked,
    colors
  } = props;
  const [series, setSeries] = React.useState(makeSeries(seriesCount, points));
  const [mounted, setMounted] = React.useState(false);
const options = {};
React.useEffect(() => {
    setSeries(makeSeries(seriesCount, points));
  }, [seriesCount, points]);
return (
    <>
      <Frame width={width} height={height} background="none">
        <Chart
          options={options}
          series={series}
          type={chartType}
          width={width}
          height={height}
        />
      </Frame>
    </>
  );
};
function makeSeries(seriesCount: number, points: number) {
  function genDateValue(n: number) {
    return Array(n)
      .fill(1)
      .map((d, i) => {
        return {
          date: new Date(Date.now() - i * 3600000),
          value: Math.max(250, (Math.random() * 3000) | 0)
        };
      });
  }
  return Array(seriesCount)
    .fill({
      data: []
    })
    .map(s => {
      return {
        data: genDateValue(points).map(v => [v.date, v.value])
      };
    });
}
Charting.defaultProps = {
  width: 800,
  height: 600,
  chartType: ChartTypes.Area,
  isHorizontal: false,
  points: 5,
  seriesCount: 1,
  fillType: "gradient",
  fillOpacity: 0.8,
  stroke: 5,
  showStroke: true,
  strokeCurve: "smooth",
  palette: "palette1",
  showToolbar: true,
  showLegend: true,
  showXaxis: true,
  showYaxis: false,
  isStacked: false,
  colors: []
} as Props;


Давайте передадим в график правильные параметры, чтобы он выглядел лучше:

const options = {
    colors:
      colors.length > 0 && palette == "custom"
        ? colors
            .map(item => {
              if (item.startsWith("var("))
                return item.match(/rgba?\(.*\d\)/).pop();
              return item;
            })
            .map(d => Color.toHexString(Color(d)))
        : undefined,
    theme: {
      palette: palette
    },
    chart: {
      id: "dogchart",
      type: chartType,
      stacked: isStacked,
      toolbar: {
        show: showToolbar
      },
      events: {
        mounted: function(chartContext, config) {
          setMounted(true);
        },
        dataPointSelection: props.onDataPointSelection,
        dataPointMouseEnter: props.onDataPointMouseEnter,
        dataPointMouseLeave: props.onDataPointMouseLeave
      }
    },
    plotOptions: {
      bar: {
        horizontal: isHorizontal
      }
    },
    fill: {
      opacity: fillOpacity,
      type: fillType
    },
    stroke: {
      show: showStroke,
      curve: strokeCurve,
      width: stroke
    },
    xaxis: {
      type: "datetime",
      labels: {
        show: showXaxis
      }
    },
    yaxis: {
      show: showYaxis
    },
    dataLabels: {
      enabled: false
    },
    noData: {
      text: "No Data"
    },
    legend: {
      show: showLegend
    },
    tooltip: {
      fillSeriesColor: true
    }
  };


Теперь у нас должна получиться красивая диаграмма:

Сейчас нам нужно исправить две вещи:

  • Добавьте миниатюру компонента, которая появляется на боковой панели
  • Добавление элементов управления во Framer X, чтобы мы могли настраивать параметры из него

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

<>
  <Frame visible={!mounted} width="100%" height="100%" background="white">
    <svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg">
      <text x="0" y="13">
        🐶📊
      </text>
    </svg>
  </Frame>
  <Frame width={width} height={height} background="none">
    <Chart
      options={options}
      series={series}
      type={chartType}
      width={width}
      height={height}
    />
  </Frame>
</>

Для добавления элементов управления во Framer X мы будем использовать метод addPropertyControls из Framer API.

addPropertyControls(Charting, {
  chartType: {
    type: ControlType.Enum,
    options: Object.keys(ChartTypes).map(v => ChartTypes[v]),
    optionTitles: Object.keys(ChartTypes),
    title: "Type 📊",
    defaultValue: ChartTypes.Area
  },
  isHorizontal: {
    type: ControlType.Boolean,
    defaultValue: false,
    hidden(props) {
      return props.chartType != ChartTypes.Bar;
    },
    title: "Horizontal"
  },
  points: {
    type: ControlType.Number,
    min: 0,
    defaultValue: 5,
    title: "Points 🔢"
  },
  seriesCount: {
    type: ControlType.Number,
    min: 0,
    defaultValue: 1,
    title: "Series Count 🔢"
  },
  fillType: {
    type: ControlType.Enum,
    options: ["solid", "gradient"],
    optionTitles: ["Solid", "Gradient"],
    defaultValue: "solid",
    title: "Fill type 🌈"
  },
  fillOpacity: {
    type: ControlType.Number,
    max: 1,
    min: 0,
    step: 0.1,
    defaultValue: 0.8,
    title: "↳ Opacity",
    hidden(props) {
      return props.fillType != "solid";
    }
  },
  showStroke: {
    type: ControlType.Boolean,
    defaultValue: true,
    title: "Stroke 🌀"
  },
  stroke: {
    type: ControlType.Number,
    defaultValue: 5,
    title: "↳ Width",
    hidden(props) {
      return props.showStroke == false;
    }
  },
  strokeCurve: {
    type: ControlType.Enum,
    options: ["smooth", "straight", "stepline"],
    optionTitles: ["Smooth", "Straight", "Stepline"],
    defaultValue: "smooth",
    title: "Curve 〰️"
  },
  palette: {
    type: ControlType.Enum,
    title: "Palette 🎨",
    options: [
      ...Array(10)
        .fill("palette")
        .map((v, i) => `${v}${i + 1}`),
      "custom"
    ],
    optionTitles: [
      ...Array(10)
        .fill("Palette")
        .map((v, i) => `${v} ${i + 1}`),
      "Custom"
    ]
  },
  showXaxis: {
    type: ControlType.Boolean,
    defaultValue: true,
    title: "X-axis 👁"
  },
  showYaxis: {
    type: ControlType.Boolean,
    defaultValue: false,
    title: "Y-axis 👁"
  },
  showLegend: {
    type: ControlType.Boolean,
    defaultValue: true,
    title: "Legend ℹ️"
  },
  showToolbar: {
    type: ControlType.Boolean,
    defaultValue: true,
    title: "Toolbar 🎛"
  },
  isStacked: {
    type: ControlType.Boolean,
    defaultValue: false,
    title: "Stacked 📚"
  },
  colors: {
    type: ControlType.Array,
    title: "Colors 🌈",
    propertyControl: {
      type: ControlType.Color,
      defaultValue: Color.toRgbString(Color.random(0.4))
    },
    hidden(props) {
      return props.palette != "custom";
    }
  }
});


Теперь у нас есть и эскиз, и элементы управления свойствами во Framer X!

Пришло время поиграть с ним или начать проектировать пользовательский интерфейс на его основе.

Если вы хотите начать использовать этот компонент в своем проекте, вы можете получить его в Framer Store:



Вы можете посетить репозиторий кода на GitHub:



📊↔️🕹 Графики + интерфейс

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

Файл имеет 3 слоя:

  1. График
  2. Базовый макет, импортированный из другого инструмента
  3. Рамка строки при наведении

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

Теперь мы добавляем новую функцию hovered в файл переопределения, который будет читать выбранную серию и обновлять ее в переменной data.

import { Override, Data } from "framer";
const TableY = 416;
const rowHeight = 32;
const data = Data({
  hoverPos: TableY,
  seriesIndex: 0
});
export function hovered(props): Override {
  return {
    onDataPointMouseEnter(a, b, c) {
      console.log("Hoverer Series:", c.seriesIndex);
      data.seriesIndex = c.seriesIndex + 1;
    },
    onDataPointMouseLeave() {
      data.seriesIndex = 0;
    }
  };
}

Затем мы будем использовать seriesIndex, чтобы вычислить положение Y слоя hover и обновить его.

export function calcHoverPos(props): Override {
  const top = data.hoverPos + data.seriesIndex * (rowHeight + 1);
return {
    top: top,
    visible: data.seriesIndex > 0
  };
}

Убедитесь, что мы применяем функцию calcHoverPos как переопределение к слою hover:

Результат

Преодоление разрыва

Визуализация данных - это один из способов, позволяющих аудитории извлекать важный контент из больших объемов информации. При правильном использовании он может эффективно устранить разрыв между техническими результатами и полезными интерпретациями. Короче говоря, это может помочь вам ответить на вопрос: «Почему меня это должно волновать?»

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

Я надеюсь, что эта статья стала полезным руководством для преодоления разрыва между проектированием пользовательского интерфейса и визуализацией данных. Вы можете обратиться ко мне с вопросами в комментариях или на jgog.in.

Это всего лишь пример проектирования с визуализацией данных в Datadog. Если вы хотите поработать над такими классными вещами? Присоединяйтесь к нам.