предисловие
Однажды, когда я просматривал сообщество, я увидел сообщение:
react-dynamic-charts — библиотека React для визуализации динамических данных
Это библиотека, написанная иностранным боссом на конкурсе кода на саммите его компании:react-dynamic-charts
, для создания визуализаций динамических диаграмм из динамических данных.Он разработан, чтобы быть очень гибким, позволяя вам контролировать каждый элемент и событие внутри. Метод использования также очень прост, а исходный код также очень доработан, что стоит изучить.
Но поскольку он дает многоAPI
, не способствует пониманию исходного кода. Таким образом, следующая реализация несколько упрощена:
1. Подготовьте общие служебные функции
1. getRandomColor
:Случайный цвет
const getRandomColor = () => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)]
}
return color;
};
2. translateY
: Заполнить смещение по оси Y
const translateY = (value) => {
return `translateY(${value}px)`;
}
2. ИспользуйтеuseState Hook
объявить переменные состояния
Начнем писать компонентыDynamicBarChart
const DynamicBarChart = (props) => {
const [dataQueue, setDataQueue] = useState([]);
const [activeItemIdx, setActiveItemIdx] = useState(0);
const [highestValue, setHighestValue] = useState(0);
const [currentValues, setCurrentValues] = useState({});
const [firstRun, setFirstRun] = useState(false);
// 其它代码...
}
1. useState
Простое понимание:
const [属性, 操作属性的方法] = useState(默认值);
2. Анализ переменных
-
dataQueue
: Исходный массив данных текущей операции -
activeItemIdx
: какой "кадр" -
highestValue
: значение данных "top" -
currentValues
: Массив обработанных данных для рендеринга -
firstRun
: время первого динамического рендеринга
3. Внутренний метод работы и перепискаuseEffect
Пожалуйста, ешьте с комментариями
// 动态跑起来~
function start () {
if (activeItemIdx > 1) {
return;
}
nextStep(true);
}
// 对下一帧数据进行处理
function setNextValues () {
// 没有帧数时(即已结束),停止渲染
if (!dataQueue[activeItemIdx]) {
iterationTimeoutHolder = null;
return;
}
// 每一帧的数据数组
const roundData = dataQueue[activeItemIdx].values;
const nextValues = {};
let highestValue = 0;
// 处理数据,用作最后渲染(各种样式,颜色)
roundData.map((c) => {
nextValues[c.id] = {
...c,
color: c.color || (currentValues[c.id] || {}).color || getRandomColor()
};
if (Math.abs(c.value) > highestValue) {
highestValue = Math.abs(c.value);
}
return c;
});
// 属性的操作,触发useEffect
setCurrentValues(nextValues);
setHighestValue(highestValue);
setActiveItemIdx(activeItemIdx + 1);
}
// 触发下一步,循环
function nextStep (firstRun = false) {
setFirstRun(firstRun);
setNextValues();
}
вести перепискуuseEffect
:
// 取原始数据
useEffect(() => {
setDataQueue(props.data);
}, []);
// 触发动态
useEffect(() => {
start();
}, [dataQueue]);
// 设触发动态间隔
useEffect(() => {
iterationTimeoutHolder = window.setTimeout(nextStep, 1000);
return () => {
if (iterationTimeoutHolder) {
window.clearTimeout(iterationTimeoutHolder);
}
};
}, [activeItemIdx]);
useEffect
Пример:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
почему вeffect
вернуть функцию?
Этоeffect
Дополнительный механизм очистки. каждыйeffect
Оба могут возвращать функцию очистки. Это объединяет логику добавления и удаления подписок.
4. Организовать рендерингDom
Данные
const keys = Object.keys(currentValues);
const { barGapSize, barHeight, showTitle } = props;
const maxValue = highestValue / 0.85;
const sortedCurrentValues = keys.sort((a, b) => currentValues[b].value - currentValues[a].value);
const currentItem = dataQueue[activeItemIdx - 1] || {};
-
keys
: индекс каждого набора данных -
maxValue
: максимальная ширина диаграммы -
sortedCurrentValues
: Сортировка каждого набора данных, это влияет на динамический рендеринг. -
currentItem
: необработанные данные для каждой группы
5. Начать рендеринг...
Общая логика такова:
- по разным
Props
, данные после кругового расположения:sortedCurrentValues
- Вычислить ширину, возвращая
label
,bar
,value
- Срабатывание по расчетной высоте
transform
.
<div className="live-chart">
{
<React.Fragment>
{
showTitle &&
<h1>{currentItem.name}</h1>
}
<section className="chart">
<div className="chart-bars" style={{ height: (barHeight + barGapSize) * keys.length }}>
{
sortedCurrentValues.map((key, idx) => {
const currentValueData = currentValues[key];
const value = currentValueData.value
let width = Math.abs((value / maxValue * 100));
let widthStr;
if (isNaN(width) || !width) {
widthStr = '1px';
} else {
widthStr = `${width}%`;
}
return (
<div className={`bar-wrapper`} style={{ transform: translateY((barHeight + barGapSize) * idx), transitionDuration: 200 / 1000 }} key={`bar_${key}`}>
<label>
{
!currentValueData.label
? key
: currentValueData.label
}
</label>
<div className="bar" style={{ height: barHeight, width: widthStr, background: typeof currentValueData.color === 'string' ? currentValueData.color : `linear-gradient(to right, ${currentValueData.color.join(',')})` }} />
<span className="value" style={{ color: typeof currentValueData.color === 'string' ? currentValueData.color : currentValueData.color[0] }}>{currentValueData.value}</span>
</div>
);
})
}
</div>
</section>
</React.Fragment>
}
</div>
6. Определите общийpropTypes
а такжеdefaultProps
:
DynamicBarChart.propTypes = {
showTitle: PropTypes.bool,
iterationTimeout: PropTypes.number,
data: PropTypes.array,
startRunningTimeout: PropTypes.number,
barHeight: PropTypes.number,
barGapSize: PropTypes.number,
baseline: PropTypes.number,
};
DynamicBarChart.defaultProps = {
showTitle: true,
iterationTimeout: 200,
data: [],
startRunningTimeout: 0,
barHeight: 50,
barGapSize: 20,
baseline: null,
};
export {
DynamicBarChart
};
7. Как использовать
import React, { Component } from "react";
import { DynamicBarChart } from "./DynamicBarChart";
import helpers from "./helpers";
import mocks from "./mocks";
import "react-dynamic-charts/dist/index.css";
export default class App extends Component {
render() {
return (
<DynamicBarChart
barGapSize={10}
data={helpers.generateData(100, mocks.defaultChart, {
prefix: "Iteration"
})}
iterationTimeout={100}
showTitle={true}
startRunningTimeout={2500}
/>
)
}
}
1. Пакетная генерацияMock
данные
helpers.js
:
function getRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
};
function generateData(iterations = 100, defaultValues = [], namePrefix = {}, maxJump = 100) {
const arr = [];
for (let i = 0; i <= iterations; i++) {
const values = defaultValues.map((v, idx) => {
if (i === 0 && typeof v.value === 'number') {
return v;
}
return {
...v,
value: i === 0 ? this.getRandomNumber(1, 1000) : arr[i - 1].values[idx].value + this.getRandomNumber(0, maxJump)
}
});
arr.push({
name: `${namePrefix.prefix || ''} ${(namePrefix.initialValue || 0) + i}`,
values
});
}
return arr;
};
export default {
getRandomNumber,
generateData
}
mocks.js
:
import helpers from './helpers';
const defaultChart = [
{
id: 1,
label: 'Google',
value: helpers.getRandomNumber(0, 50)
},
{
id: 2,
label: 'Facebook',
value: helpers.getRandomNumber(0, 50)
},
{
id: 3,
label: 'Outbrain',
value: helpers.getRandomNumber(0, 50)
},
{
id: 4,
label: 'Apple',
value: helpers.getRandomNumber(0, 50)
},
{
id: 5,
label: 'Amazon',
value: helpers.getRandomNumber(0, 50)
},
];
export default {
defaultChart,
}
Готова нищенская версия визуализации динамической таблицы лидеров.
8. Полный код
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import './styles.scss';
const getRandomColor = () => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)]
}
return color;
};
const translateY = (value) => {
return `translateY(${value}px)`;
}
const DynamicBarChart = (props) => {
const [dataQueue, setDataQueue] = useState([]);
const [activeItemIdx, setActiveItemIdx] = useState(0);
const [highestValue, setHighestValue] = useState(0);
const [currentValues, setCurrentValues] = useState({});
const [firstRun, setFirstRun] = useState(false);
let iterationTimeoutHolder = null;
function start () {
if (activeItemIdx > 1) {
return;
}
nextStep(true);
}
function setNextValues () {
if (!dataQueue[activeItemIdx]) {
iterationTimeoutHolder = null;
return;
}
const roundData = dataQueue[activeItemIdx].values;
const nextValues = {};
let highestValue = 0;
roundData.map((c) => {
nextValues[c.id] = {
...c,
color: c.color || (currentValues[c.id] || {}).color || getRandomColor()
};
if (Math.abs(c.value) > highestValue) {
highestValue = Math.abs(c.value);
}
return c;
});
console.table(highestValue);
setCurrentValues(nextValues);
setHighestValue(highestValue);
setActiveItemIdx(activeItemIdx + 1);
}
function nextStep (firstRun = false) {
setFirstRun(firstRun);
setNextValues();
}
useEffect(() => {
setDataQueue(props.data);
}, []);
useEffect(() => {
start();
}, [dataQueue]);
useEffect(() => {
iterationTimeoutHolder = window.setTimeout(nextStep, 1000);
return () => {
if (iterationTimeoutHolder) {
window.clearTimeout(iterationTimeoutHolder);
}
};
}, [activeItemIdx]);
const keys = Object.keys(currentValues);
const { barGapSize, barHeight, showTitle, data } = props;
console.table('data', data);
const maxValue = highestValue / 0.85;
const sortedCurrentValues = keys.sort((a, b) => currentValues[b].value - currentValues[a].value);
const currentItem = dataQueue[activeItemIdx - 1] || {};
return (
<div className="live-chart">
{
<React.Fragment>
{
showTitle &&
<h1>{currentItem.name}</h1>
}
<section className="chart">
<div className="chart-bars" style={{ height: (barHeight + barGapSize) * keys.length }}>
{
sortedCurrentValues.map((key, idx) => {
const currentValueData = currentValues[key];
const value = currentValueData.value
let width = Math.abs((value / maxValue * 100));
let widthStr;
if (isNaN(width) || !width) {
widthStr = '1px';
} else {
widthStr = `${width}%`;
}
return (
<div className={`bar-wrapper`} style={{ transform: translateY((barHeight + barGapSize) * idx), transitionDuration: 200 / 1000 }} key={`bar_${key}`}>
<label>
{
!currentValueData.label
? key
: currentValueData.label
}
</label>
<div className="bar" style={{ height: barHeight, width: widthStr, background: typeof currentValueData.color === 'string' ? currentValueData.color : `linear-gradient(to right, ${currentValueData.color.join(',')})` }} />
<span className="value" style={{ color: typeof currentValueData.color === 'string' ? currentValueData.color : currentValueData.color[0] }}>{currentValueData.value}</span>
</div>
);
})
}
</div>
</section>
</React.Fragment>
}
</div>
);
};
DynamicBarChart.propTypes = {
showTitle: PropTypes.bool,
iterationTimeout: PropTypes.number,
data: PropTypes.array,
startRunningTimeout: PropTypes.number,
barHeight: PropTypes.number,
barGapSize: PropTypes.number,
baseline: PropTypes.number,
};
DynamicBarChart.defaultProps = {
showTitle: true,
iterationTimeout: 200,
data: [],
startRunningTimeout: 0,
barHeight: 50,
barGapSize: 20,
baseline: null,
};
export {
DynamicBarChart
};
styles.scss
:
.live-chart {
width: 100%;
padding: 20px;
box-sizing: border-box;
position: relative;
text-align: center;
h1 {
font-weight: 700;
font-size: 60px;
text-transform: uppercase;
text-align: center;
padding: 20px 10px;
margin: 0;
}
.chart {
position: relative;
margin: 20px auto;
}
.chart-bars {
position: relative;
width: 100%;
}
.bar-wrapper {
display: flex;
flex-wrap: wrap;
align-items: center;
position: absolute;
top: 0;
left: 0;
transform: translateY(0);
transition: transform 0.5s linear;
padding-left: 200px;
box-sizing: border-box;
width: 100%;
justify-content: flex-start;
label {
position: absolute;
height: 100%;
width: 200px;
left: 0;
padding: 0 10px;
box-sizing: border-box;
text-align: right;
top: 50%;
transform: translateY(-50%);
font-size: 16px;
font-weight: 700;
display: flex;
justify-content: flex-end;
align-items: center;
}
.value {
font-size: 16px;
font-weight: 700;
margin-left: 10px;
}
.bar {
width: 0%;
transition: width 0.5s linear;
}
}
}
Оригинальный адрес проекта:react-dynamic-charts
Эпилог
Мне всегда было интересно реализовать визуализацию динамических списков лидеров, но большинство из них основано наD3
илиecharts
выполнить.
И эта библиотека не только отделена от графической библиотеки, но и используетсяReact 16
новые особенности. Это также дало мне понятьReact Hook
магии.
❤️ После прочтения трех вещей
Если вы найдете этот контент вдохновляющим, я хотел бы пригласить вас сделать мне три небольших одолжения:
- Ставьте лайк, чтобы больше людей увидело этот контент
- Обратите внимание на паблик «Учитель фронтенд-убеждения», и время от времени делитесь оригинальными знаниями.
- Также смотрите другие статьи
- Те шаблоны проектирования, которые вы используете непреднамеренно (1) - шаблоны создания
- [«Король библиотеки визуализации данных» D3.js быстро приступает к работе с приложениями Vue
](nuggets.capable/post/684490…)
- Руководство "True® Full Stack Road" для веб-интерфейсной разработки
- «Практика Vue» — плагин Vue CLI за 5 минут
- Вооружите свой интерфейсный проект «Практикой Vue»
- «Intermediate and Advanced Front-End Interview» JavaScript Рукописный код Invincible Cheats
- «Узнайте из исходного кода» ответы на вопросы Vue, которые интервьюеры не знают
- JS-операция «Узнать из исходного кода» в исходном коде Vue
- Правильная позиция для обновления vue-cli3 в проекте "Vue Practice"
- Почему вы до сих пор не можете понять цепочку областей видимости JavaScript?