代码之家  ›  专栏  ›  技术社区  ›  Joel

D3.js JSON数据到折线图

  •  0
  • Joel  · 技术社区  · 6 年前

    我之所以从上一个图切换到这个图,是因为我需要这个示例的响应性 here here .

    然而,我似乎无法将数据输入到这个图表中,我不确定我做错了什么。在我的道路上 d 我得到了一堆MNaN,这表明我没有正确地传递数据(它很可能不是一个数字,因此d3的路径生成器在第一个错误时抛出这些随机值(在本例中这是好的,因为第一个数字无效))。。。

    Error: <path> attribute d: Expected number, "MNaN,-552LNaN,-60…".

    到目前为止,我对造成错误的原因的最大疑问是 d3.extent

    我的数据对象如下所示:

    [{
        "name": "Data1",
        "data" :
        {
        "resultset": [
    
            [
                1.42,
                "2018-09-18 00:00:00"
            ],
            [
                1.92,
                "2018-09-18 01:00:00"
            ],
            [
                1.32,
                "2018-09-18 10:33:35"
            ],
            [
                0.00,
                "2018-09-18 10:43:35"
            ]    
        ],
        "metadata": [
            {}
        ],
        "totalrows": 8
    }}]
    

    有些人可能想知道数据来自何处,所以我将发布以下组件片段:

    export class MachineGraphComponent implements AfterViewInit, OnInit, OnDestroy {
    
        eventListeners: Function[] = [];
        machines: GraphData[];
        static componentCount: number = 0;
        //graph settings
    
        @Input() datasources: { name: any, data: PentahoResponse[] }[]; //Here
        @Input() enableAxisX = 0;
        @Input() enableAxisY = 0;
    

    我的代码(在NgAfterViewInit中):

    var datasources = this.datasources;
    var graphSettings = {
        enableAxisX: this.enableAxisX,
        enableAxisY: this.enableAxisY
    }
    
    var currentId = this.thisId;
    var drawGraph = function (datasources) {
    
        $('#chart' + currentId + '.chart').empty();
    
        // Define margins
        var margin = { top: 20, right: 20, bottom: 30, left: 20 },
            width = parseInt(d3.select('#chart' + currentId + '.chart').style("width")) - margin.left - margin.right,
            height = parseInt(d3.select('#chart' + currentId + '.chart').style("height")) - margin.top - margin.bottom;
    
        // Define date parser
        var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;
        //I have tried this one in extent, like in the example linked above ^
        // Define scales
        var xScale = d3.time.scale().range([0, width]);
        var yScale = d3.scale.linear().range([height, 0]);
        var color = d3.scale.ordinal()
            .range(["#8c510a", "#dfc27d", "#35978f"]);
    
        // Define axes
        var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
        var yAxis = d3.svg.axis().scale(yScale).orient("left");
    
    
        // Define lines
        var line = d3.svg.line().interpolate("basis")
            .x(function (d) { return xScale(d["date"]); })
            .y(function (d) { return yScale(d["value"]); });
    
        // Define svg canvas
        var svg = d3.select('#chart' + currentId + '.chart').append('svg')
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
    
        // Read in data
        var datasource: {
            actualValues: any,
            date: string
        }[] = [];
        datasources.forEach(source => {
            source.data.resultset.forEach(data => {
                datasource.push({
                    actualValues: data[0],
                    date: data[1]
                });
            });
        });
        // Format the data field
        // var data = datasource.slice(); //should i use this?
        var format = d3.time.format("%b %e %Y");
        var dateFn = function (d) {
    
            return format.parse(d.date);
        };
        // tried to create a dateparser, but didnt solve the problem. 
    
        // Set the color domain equal to the three product categories
        datasource.forEach(x => {
            x.date = dateFn(x);
        });
    
        var DatasourceNames = d3.keys(datasources.map(function (d) {
            return d.name;
        }));
        // console.log(datasources.map(function(d){console.log("dwadwa",d.name); return d.name;}));
        color.domain(DatasourceNames);
    
        var values = DatasourceNames.map(function (category) {
            // console.log("here: ",datasources[category].data.resultset);
            return {
                category: category,
                datapoints: datasource.map(function (d) {
                    return {
                        value: d.actualValues,
                        date: d.date
                    }
                })
            }
        })
    
        // Set the domain of the axes
        xScale.domain(d3.extent(datasource, function (d) { return d; }));
        yScale.domain([0.25, 0.5]);
    
        // Place the axes on the chart
        svg.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + height + ")")
            .style("opacity", graphSettings.enableAxisX)
            .call(xAxis);
    
        svg.append("g")
            .attr("class", "y axis")
            .style("opacity", graphSettings.enableAxisY)
            .call(yAxis)
            .append("text")
            .attr("class", "label")
            .attr("y", 6)
            .attr("dy", ".71em")
            .attr("dx", ".71em")
            .style("text-anchor", "beginning")
            .text("Product Concentration");
    
        var products = svg.selectAll(".category")
            .data(values)
            .enter().append("g")
            .attr("class", "category");
    
        products.append("path")
            .attr("class", "line")
            .attr("d", function (d) { console.log(d.datapoints); return line(d.datapoints); })
            .style("stroke", function (d) { return color(d.category); });
    
        // console.log(JSON.stringify(d3.values(values), null, 2)) // to view the structure
        // console.log(values.map(function()))
    
        // Define responsive behavior
        var resize = function () {
            for (var i = 0; i < MachineGraphComponent.componentCount; i++) {
    
                var svg = d3.select('#chart' + i + '.chart');
    
                var width = parseInt(d3.select('#chart' + i + '.chart').style("width")) - margin.left - margin.right,
                    height = parseInt(d3.select('#chart' + i + '.chart').style("height")) - margin.top - margin.bottom;
    
                // console.log(i, MachineGraphComponent.componentCount);
                // Update the range of the scale with new width/height
                xScale.range([0, width]);
                yScale.range([height, 0]);
    
                // Update the axis and text with the new scale
                svg.select('.x.axis')
                    .attr("transform", "translate(0," + height + ")")
                    .call(xAxis);
    
                svg.select('.y.axis')
                    .call(yAxis);
    
                // Force D3 to recalculate and update the line
                svg.selectAll('.line')
                    .attr("d", function (d) { return line(d.datapoints); });
    
                // Update the tick marks
                xAxis.ticks(Math.max(width / 75, 2));
                yAxis.ticks(Math.max(height / 50, 2));
            }
        };
    
        // Call the resize function whenever a resize event occurs
        d3.select(window).on('resize', resize);
    
        // Call the resize function
        resize();
    };
    (function () {
        drawGraph(datasources);
        // drawGraph(dataObj2);
    })();
    

    尝试添加行时:

        products.append("path")
            .attr("class", "line")
            .attr("d", function (d) { console.log(d.datapoints); return line(d.datapoints); })
            .style("stroke", function (d) { return color(d.category); });
    

    控制台日志告诉我:

    enter image description here

    如果我需要澄清或解释一些事情,请告诉我。

    1 回复  |  直到 6 年前
        1
  •  0
  •   mike jwezorek    6 年前

    是的, D3.extent() 不明白你在喂它什么。我建议直接传递最小值和最大值。最小和最大日期在数据结构的深处隐藏了两个级别,因此需要遍历两个级别的数组才能找到它们。我首先将您的时间转换应用于原始数据结构,这样就可以从D3.min命令获得正确的输出。麦克斯也是这样

    //start by cleaning the date in your data
    // the original data structure will naow have a nice D3 date.
    var TimeFormatter = d3.time.format("%Y-%m-%d %H:%M:%S");
    datasources.forEach(source => {
      source.data.resultset.forEach(data => {
        data[1] = TimeFormatter.parse(data[1]);
      });   
    });
    
    //find the overall min of all lines and overall max of all limes by enumerating the datasources, then the data in each data source. I'm mapping the time data[1] to the d3.min or d3.max function.
    var XoverallMin = d3.min(datasources, function(ds) { return d3.min(ds.data.resultset, function(data){return data[1]}) });
    var XoverallMax = d3.max(datasources, function(ds) { return d3.max(ds.data.resultset, function(data){return data[1]}) });
    
    
      //once we've found the min and max then setting the domain of the xScale is easy. :-)
         // Set the domain of the axes
          var xScale = d3.time.scale()
                            .range([0, 200])
                            .domain([XoverallMin,XoverallMax]);
    

    你也在绘制你所有的 resultset datasource 然后将同一组数据(组合所有数据)应用于每个唯一的数据源名称。相反,如果您为每个数据源创建一个对象,这可能会有所帮助。

       // you had only one datasource total, 
        // i changed it so you have a datasource within each of the sources
        datasources.forEach(source => {
            source.datasource = Array();
    
            source.data.resultset.forEach(data => {
                source.datasource.push({
                    value: data[0],
                    date: data[1]
                });   
            });
    
       });
    
       // ... later in the code,
       // when you create your values object, the data will be separated by  
       //  data source.
        var values = datasources.map(function (source) {
            return {
                category: source.name,
            datapoints: source.datasource //the new object we made and added to
                                          // each source above
            };
          });