D3.js JSON数据转折线图

时间:2018-10-04 07:21:39

标签: typescript d3.js

我从上一张切换到该图的原因是因为我需要本示例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 个答案:

答案 0 :(得分:0)

是的,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
        };
      });

这不是一个完整的解决方案,总体结构中有很多错误,但是应该可以克服一些特定的障碍。