2017-02-02 3 views
1

Ich baue ein gestapeltes Balkendiagramm mit d3 v4.4.4 in react.js v15.3.0. Im Folgenden finden Sie die Daten, die ich mein gestapeltes Balkendiagramm bauen bin mit:D3 stack() überspringt erstes Objekt in meinem Datenfeld

[ 
    { timestamp: "2006", source1: "20", source2: "20", source3: "20", source4: '20'}, 
    { timestamp: "2007", source1: "70", source2: "50", source3: "10", source4: '70'}, 
    { timestamp: "2008", source1: "80", source2: "50", source3: "60", source4: '40'}, 
    { timestamp: "2009", source1: "30", source2: "20", source3: "40", source4: '50'}, 
    { timestamp: "2010", source1: "70", source2: "20", source3: "90", source4: '20'} 
] 

ich das gestapeltes Balkendiagramm bis zeigen seen here bekommen habe, aber es ist nicht in der Anordnung der Daten das erste Objekt zeigt. Stattdessen dupliziert das gestapelte Balkendiagramm das letzte Objekt in dem Datenfeld und verwendet es für den ersten und den letzten gestapelten Balken im Diagramm.

const stack = d3.stack().keys(keys) 

const layers = stack(chartDataWorkingCopy) 

this was the result of logging layers to the console

Wie Sie aus dem obigen Screenshot der ersten und letzten Anordnungen sind die gleichen, und das erste Objekt sehen kann (dh {Zeitstempel: "2006", source1: "20", source2: "20", source3: "20", source4: '20 '}) aus den Originaldaten, die ich zum Erstellen des Diagramms verwendet habe, ist nicht vorhanden.

Kann mir jemand erklären, warum d3 über das erste Objekt im Array springt und das letzte beim Erstellen des gestapelten Balkendiagramms dupliziert?

Im Folgenden ist der Quellcode für die gestapeltes Balkendiagramm Komponente:

import React, { PropTypes } from 'react' 
import * as d3 from 'd3' 
import _ from 'lodash' 

class StackedBarChart extends React.Component { 
    static propTypes = { 
    chartData: PropTypes.array, 
    barWidth: PropTypes.number, 
    barOffset: PropTypes.number, 
    chartHeight: PropTypes.number, 
    chartWidth: PropTypes.number 
    } 

    static defaultProps = { 
    chartData: [ 
     { timestamp: "2006", source1: "20", source2: "20", source3: "20", source4: '20'}, 
     { timestamp: "2007", source1: "70", source2: "50", source3: "10", source4: '70'}, 
     { timestamp: "2008", source1: "80", source2: "50", source3: "60", source4: '40'}, 
     { timestamp: "2009", source1: "30", source2: "20", source3: "40", source4: '50'}, 
     { timestamp: "2010", source1: "70", source2: "20", source3: "90", source4: '20'} 
    ], 
    barWidth: 10, 
    barOffset: 5, 
    chartHeight: 200, 
    chartWidth: 300 
    } 

    componentDidMount() { 
    this.renderChart() 
    } 

    componentDidUpdate(prevProps){ 
    if(!_.isEqual(this.props, prevProps)){ 
     d3.select('svg').remove() 
     this.renderChart() 
    } 
    } 

    splitTheDifference(data) { 
    let str = String(data) 
    const points = str.split(',') 
    return (points[1] - points[0]) 
    } 

    renderChart() { 
    const { chartData, barWidth, barOffset, chartHeight, chartWidth} = this.props 

    let chartDataWorkingCopy = [...chartData] 

    // keep the bars from going off the page 
    const maxBars = (chartWidth/(barWidth + 2)) 

    if (chartDataWorkingCopy.length > maxBars) { 
     chartDataWorkingCopy = chartDataWorkingCopy.slice(-Math.floor(maxBars)) 
    } 

    const keys = _.remove(_.keys(_.extend.apply({}, chartDataWorkingCopy)), (d) => { 
     return d !== 'timestamp' 
    }) 

    const xScale = d3.scaleBand().range([0, chartWidth]).padding(0.1) 
    const yScale = d3.scaleLinear().range([chartHeight, 0]) 
    const color = ['hsl(8, 82%, 50%)', 'hsl(76, 96%, 50%)', 'hsl(178, 99%, 50%)', 'hsl(302, 100%, 50%)', 'hsl(58, 98%, 50%)', 'hsl(144, 100%, 50%)'] 
    const xAxis = d3.axisBottom(xScale).tickFormat(d3.timeFormat("%b")) 
    const yAxis = d3.axisLeft(yScale) 
    const width = chartWidth 
    const height = chartHeight 

    let svg = d3.select('.barChartContainer').append("svg") 
       .attr('width', width) 
       .attr('height', height) 
       .append('g') 

    const stack = d3.stack().keys(keys) 

    const layers = stack(chartDataWorkingCopy) 
     xScale.domain(chartDataWorkingCopy.map(function(d) { return d.timestamp })) 
     yScale.domain([0, d3.max(layers[layers.length - 1], function(d) { 
      return d[0] + d[1] 
     }) ]).nice() 

    const layer = svg.selectAll('.layer') 
      .data(layers) 
      .enter().append('g') 
      .attr('class', 'layer') 
      .style('fill', function(d, i) { return color[i] }) 

     layer.selectAll("rect") 
       .data(function(d) { 
      return d 
     }) 
      .enter().append("rect") 
       .attr("x", function(d, i) { 
      return i * 12 
     }) 
       .attr("y", function(d) { return yScale(d[1]) }) 
       .attr("height", function(d) { return yScale(d[0]) - yScale(d[1]) }) 
       .attr("width", 10) 
     .on('mouseover', (d) => { 
      d3.select('#tooltip') 
      .classed('hidden', false) 
      .style('position', 'absolute') 
      .style('background', '#333333') 
      .style('color', '#fff') 
      .style('padding', 10) 
      .style('left', `${d3.event.pageX}px`) 
      .style('top', `${d3.event.pageY - 80}px`) 
      .select('#value') 
       .text(this.splitTheDifference(d)) 
     }) 
     .on('mouseout', (d) => { 
      d3.select('#tooltip') 
      .classed('hidden', true) 
     }) 



    } 

    render(){ 

    return(
     <div className={ 'barChartContainer' }> 
     <div id='tooltip' className='hidden'> 
      <p id='value'></p> 
     </div> 
     </div> 
    ) 
    } 
} 

export default StackedBarChart 

Antwort

0

Nun, d3.stack() ist das Überspringen nicht das erste Objekt in der Datenmatrix.

Das Problem ist, dass dieser Code ...

const keys = _.remove(_.keys(_.extend.apply({}, chartDataWorkingCopy)), (d) => { 
    return d !== 'timestamp' 
}) 

... nicht nur die Schlüssel bekommen, sondern auch Ihre chartDataWorkingCopy Array ändern, die noch auf Ihre chartData Array verweist.

Gerade jetzt, können Sie denken, dass Sie Ihre ursprüngliche Array klonen hier:

let chartDataWorkingCopy = [...chartData]; 

Aber leider bist du nicht. Sie können ein Array von Objekten nicht mit dem Spread-Operator klonen. Die documentation sagt:

In der Regel gehen die Spread-Operatoren in ES2015 eine Ebene tief beim Kopieren eines Arrays. Daher sind sie zum Kopieren mehrdimensionaler Arrays ungeeignet. Dasselbe gilt für die Operatoren Object.assign() und Object Spread. Sehen Sie sich das folgende Beispiel an, um ein besseres Verständnis zu erhalten.

In Ihrem Code ändert sich also auch jede Änderung an chartDataWorkingCopychartData.

Lösung 01

Sie können die Schlüssel erhalten, ohne das Array zu ändern, mit Vanille JS oder D3, brauchen Sie nicht lodash dafür.Zum Beispiel ist dies eine sehr einfache Art und Weise die Schlüssel erhalten Sie nur D3 mit wollen und ohne die Quelle Array zu ändern:

const keys = d3.keys(chartDataWorkingCopy[0]).filter(d => d != "timestamp"); 

Lösung 02

Wenn Sie Ihre lodash Funktion zu halten, verwenden ein Verfahren, das wirklich kopiert eine tiefe Anordnung, wie folgt aus:

let chartDataWorkingCopy = JSON.parse(JSON.stringify(chartData)); 

In dieser Demo, ich bin Ihr Code zu halten (mit lodash), nur den Code zu ändern, die eine Kopie des ursprünglichen Arrays macht. Nun sind die Werte korrekt:

var chartData = [ 
 
    { timestamp: "2006", source1: "20", source2: "20", source3: "20", source4: '20'}, 
 
    { timestamp: "2007", source1: "70", source2: "50", source3: "10", source4: '70'}, 
 
    { timestamp: "2008", source1: "80", source2: "50", source3: "60", source4: '40'}, 
 
    { timestamp: "2009", source1: "30", source2: "20", source3: "40", source4: '50'}, 
 
    { timestamp: "2010", source1: "70", source2: "20", source3: "90", source4: '20'} 
 
    ]; 
 

 
let chartDataWorkingCopy = JSON.parse(JSON.stringify(chartData)); 
 

 

 
const keys = _.remove(_.keys(_.extend.apply({}, chartDataWorkingCopy)), (d) => { 
 
    return d !== 'timestamp' 
 
}) 
 

 

 
const stack = d3.stack().keys(keys) 
 

 
const layers = stack(chartData); 
 

 
console.log(layers);
<script src="https://d3js.org/d3.v4.min.js"></script> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

PS: Das Kopieren eines Arrays mit JSON.parse(JSON.stringify(array)) nur funktioniert, wenn Sie nicht aktuelle Datum Objekte in diesem Array verfügen. Im Moment sind "2006", "2007", "2008" usw. nur Strings, also funktioniert diese Methode.

Verwandte Themen