Графики с осью d3 и d3 с динамическим полем на React - PullRequest
0 голосов
/ 21 февраля 2019

Позвольте мне объяснить, что я пытаюсь выполнить: я хочу нарисовать диаграмму даты и времени, используя библиотеки d3, что позволяет мне рисовать как серию, так и ось.

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

Теперь, в чем проблема ... Идея / алгоритм, который я реализую, основан на двух простых моментах:

  1. В состоянии компонента я сохраняю размер svg и маржу, которая требуется для графика, по осям (если у меня есть метка тика в начале или в конце графика, если я не хочу, чтобы они были скрыты, мне нужен запас!)
  2. SVG берет 100% своего родителя, но ряд и скалярная функция для оси x вычисляются на svg size - margin sizes

Итак, позвольте мне показать вам пример, который также привел к проблеме (это цикл, который приводит к сбою приложения), предполагая, что каждый тик должен иметь длину не менее 50px:

  1. Компонент имеет chartWidth, равный 500px, а leftMargin и rightMargin оба установлены на 0.Скалярная функция вычисляется на chartWidth - (leftMargin + rightMargin).Таким образом, мы можем иметь 500 / 50 = 10 тик.

  2. Тики вводятся в график, НО!Один из тиков находится прямо в начале оси X, поэтому для его скрытия требуется поле (при условии 5px), чтобы не скрывать → состояние компонента обновляется так, что leftMargin = 5, rightMargin = 5,и, таким образом, chartWidth = 490.

  3. Поскольку состояние было обновлено, скалярная функция и метка вычисляются снова: у нас есть chartWidth = 490, поэтому у нас есть 490 / 9 = 9 меток. ЕСТЬ ПРОБЛЕМА!

  4. Тики вставляются в график, но теперь в начале графика нет метки. Итак, leftMarginи rightMargin установлены на 0 м и, таким образом, chartWidth снова 500

  5. Угадайте, что?Мы вернулись к шагу (1), и цикл никогда не заканчивается> _>

Я создал фрагмент, чтобы увидеть проблему, которую также можно увидеть здесь -> https://codepen.io/Gesma94/pen/vbMLrN

Если вы измените размер контейнера, вы увидите, что приложение вылетает (уменьшите его, оно очень скоро достигнет критической точки).

Самое простое решение - учитывать поля, когда я вычисляюномер тика.Таким образом, вместо:

const tickNumber = Math.floor(domainWidth / MINIMUM_X_TICK);

я бы использовал:

const tickNumber = Math.floor((chartWidth + (leftMargin*2) / MINIMUM_X_TICK);

Но, делая так, я потерял реальное MINIMUM_X_TICK, поскольку я рассматриваю не chartWidth, а containerWidth.

Более того, если я добавлю ось Y, ошибки могут возрасти, поскольку у меня будет две переменные (поля, заданные как xось и ось Y).

Итак, в отличие от ответа (я думаю), мой вопрос довольно прост: есть ли простой способ решить эту проблему?Может быть, уже есть «как», которого я не нашел (я знаю «соглашение о запасах» → https://bl.ocks.org/mbostock/3019563,, но мне нужна его «лучшая» версия).

У меня естьборолись уже несколько дней, и у меня заканчиваются идеи> _>

const MINIMUM_X_TICK = 50;
const startDate = "2017-09-10T09:21:00.002Z";
const endDate = "2017-09-30T15:43:00.081Z";

class App extends React.Component {
    xAxisRef = React.createRef();
    yAxisRef = React.createRef();
    containerRef = React.createRef();
    counter = 0;
    resizeObserver;
    
    constructor(props) {
        super(props);
        
        this.state = {
            leftMargin: 0,
            rightMargin: 0,
            topMargin: 0,
            bottomMargn: 0,
            chartWidth: 250,
            charHeight: 185,
            containerWidth: 250,
            containerHeight: 185
        }
        
        this.resizeObserver = new ResizeObserver(entries => {
            for (const entry of entries) {
                if(entry && entry.contentRect) {
                    this.onResize(entry.contentRect.width, entry.contentRect.height);
                }
            }
        });
    }

    onResize = (width, height) => {
        console.log("width is: " + width);
        this.setState(prevState => ({
            containerWidth: width,
            chartWidth: width - (prevState.leftMargin*2),
            containerHeight: height - 15
        }));
    };

    componentDidMount() {
        this.resizeObserver.observe(this.containerRef.current);
        this.injectXAxis();
    };

    componentDidUpdate() {
        this.injectXAxis();
        this.adjustMargin();
    }

    injectXAxis() {
        const {leftMargin, rightMargin, chartWidth} = this.state;
        const xAxisDOM = this.xAxisRef.current;
        
        const xScale = d3.scaleTime()
            .domain([new Date(startDate), new Date(endDate)])
            .range([leftMargin, chartWidth + leftMargin]);
        
        const timeSpan = (new Date(endDate).getTime()) - (new Date(startDate).getTime());
        const msPerPixel = timeSpan / (chartWidth);
        const every = Math.ceil((msPerPixel * MINIMUM_X_TICK) / 86400000);
        
        
        const label = d3.timeDay.every(every).range(new Date(startDate), new Date(endDate));
       
        const xAxis = d3.axisBottom(xScale).tickValues(label);
        xAxis(d3.select(xAxisDOM));
    }
       
    adjustMargin() {
        const {leftMargin, rightMargin,  chartWidth} = this.state;
        const xAxisDOM = this.xAxisRef.current;
        const xDomainDOM = d3.select(xAxisDOM).select('.domain').node();  
        const xAxisBR = xAxisDOM.getBoundingClientRect();
        const xDomainBR = xDomainDOM.getBoundingClientRect();
        
        const leftMarginRequired = xDomainBR.left - xAxisBR.left;
        const rightMarginRequired = xAxisBR.right - xDomainBR.right;
        const marginRequired = Math.ceil(d3.max([leftMarginRequired, rightMarginRequired]));
  
        if (marginRequired !== leftMargin || marginRequired !== rightMargin) {
            this.setState(prevState => ({
                leftMargin: marginRequired,
                rightMargin: marginRequired,
                chartWidth: prevState.containerWidth - (marginRequired*2)
            }));
        }
    }

    render() {
        console.log(JSON.stringify(this.state, null,4));
        
        return (
            <div className="resizer">
                <div className="svgContainer" ref={this.containerRef}>
                    <svg width={this.state.chartWidth} height={this.state.chartHeight}>
                        <g data-content="x-axis" ref={this.xAxisRef} />                
                        <g data-content="y-axis" ref={this.yAxisRef} />
                    </svg>
                </div>
            </div>
        );
    }
}

ReactDOM.render(<App />, document.getElementById('root'));
@import url(https://fonts.googleapis.com/css?family=Montserrat);

body {
    font-family: 'Montserrat', sans-serif;
}

.svgContainer {
    width: 100%;
    height: calc(100% - 15px);
}

.resizer {   
    width: 250px;
    height: 200px; 
    resize: both;
    overflow: hidden;
    background: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.1/d3.min.js"></script>

<body>
  <div id='root'></div>
</body>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...