我仍在学习编程和D3,所以请在这里忍受。
我有一个表,该表是使用D3从JSON数据生成的。我希望接下来要做的是在详细信息的上方放置一个摘要行,以输入相同但数字不同的日期。我有一个reducer函数,该函数可以显示每个条目的总和,但不确定如何将其附加到详细信息上方的表中,从而产生类似以下内容的结果:
我的归约工作已准备好摘要,那么如何将其附加到现在正在使用明细数据的表中?我认为我们必须使用nest
,但不能完全确定要走的路。我的代码如下:
const merged = [{
"date": "2018-10-09",
"Campaign_Name": "Foo - 6480_1925",
"affiliateId": "6480",
"Clicks": 6,
"Conversions": 0,
"Spend": 0.5019512028,
"affiliate": "Y_Foo_6480",
"revenue": 58.22,
"advertiser": "sky",
"spend": 0.5,
"profit": 57.72,
"profitMargin": "99",
"cpc": 0.08,
"rpc": 9.7,
"rpa": ""
}, {
"date": "2018-10-09",
"Campaign_Name": "Bar Mutual - 7157_2020",
"affiliateId": "7157",
"Clicks": 583,
"Conversions": 0,
"Spend": 166.0008698087,
"affiliate": "Y_Bar Mutual_7157",
"revenue": 2.22,
"advertiser": "Bar Mutual Insurance",
"spend": 166,
"profit": -163.78,
"profitMargin": "-7378",
"cpc": 0.28,
"rpc": 0,
"rpa": ""
}, {
"date": "2018-10-09",
"Campaign_Name": "test - Baz Deals - CAN - 4086_1743",
"affiliateId": "4086",
"Clicks": 1,
"Conversions": 0,
"Spend": 0.0108815003,
"affiliate": "Y_Mobile_OMBaz_CAN_4086",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 0.01,
"profit": -0.01,
"cpc": 0.01,
"rpc": 0,
"rpa": ""
}, {
"date": "2018-10-09",
"Campaign_Name": "test - GetStuff - 7191_2133",
"affiliateId": "7191",
"Clicks": 6,
"Conversions": 0,
"Spend": 1.3499999642,
"affiliate": "Y_GetStuff_7191",
"revenue": 0.36,
"advertiser": "Art",
"spend": 1.35,
"profit": -0.99,
"profitMargin": "-275",
"cpc": 0.22,
"rpc": 0.06,
"rpa": ""
}, {
"date": "2018-10-09",
"Campaign_Name": "test - Lawyer - 7275_2165",
"affiliateId": "7275",
"Clicks": 199,
"Conversions": 0,
"Spend": 10.2255493868,
"affiliate": "Y_Lawyer_7275",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 10.23,
"profit": -10.23,
"cpc": 0.06,
"rpc": 0,
"rpa": ""
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - 4735_2092",
"affiliateId": "4735",
"Clicks": 200,
"Conversions": 34,
"Spend": 59.1212777495,
"affiliate": "Y_Mobile-3B_OMNewCar_4735",
"revenue": 20.1,
"advertiser": "Acme, Inc. ",
"spend": 59.12,
"profit": -39.02,
"profitMargin": "-194",
"cpc": 0.3,
"rpc": 0.1,
"rpa": 0.59
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - 6586_2092",
"affiliateId": "6586",
"Clicks": 472,
"Conversions": 79,
"Spend": 61.0002093334,
"affiliate": "Y_New Cars_6586",
"revenue": 0.75,
"advertiser": "Acme, Inc. ",
"spend": 61,
"profit": -60.25,
"profitMargin": "-8033",
"cpc": 0.13,
"rpc": 0,
"rpa": 0.01
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - 6618_2092",
"affiliateId": "6618",
"Clicks": 2,
"Conversions": 1,
"Spend": 0.2018772066,
"affiliate": "Y_New Cars_6618",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 0.2,
"profit": -0.2,
"cpc": 0.1,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - 7247_1773",
"affiliateId": "7247",
"Clicks": 76,
"Conversions": 7,
"Spend": 13.9912065665,
"affiliate": "Y_New Cars_7247",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 13.99,
"profit": -13.99,
"cpc": 0.18,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - NSConvLAL - 6594_2092",
"affiliateId": "6594",
"Clicks": 905,
"Conversions": 264,
"Spend": 293.5172631741,
"affiliate": "Y_New Cars_6594",
"revenue": 1.72,
"advertiser": "Acme, Inc. ",
"spend": 293.64,
"profit": -291.8,
"profitMargin": "-16965",
"cpc": 0.32,
"rpc": 0,
"rpa": 0.01
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - NSConvLAL - 7251_2092",
"affiliateId": "7251",
"Clicks": 202,
"Conversions": 1,
"Spend": 64.9944748056,
"affiliate": "Y_New Cars_7251",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 64.99,
"profit": -64.99,
"cpc": 0.26,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - Span/Eng - 7165_1773",
"affiliateId": "7165",
"Clicks": 891,
"Conversions": 49,
"Spend": 74.5347691271,
"affiliate": "Y_New Cars_7165",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 74.53,
"profit": -74.53,
"cpc": 0.08,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - New Cars - 4713_1875",
"affiliateId": "4713",
"Clicks": 1084,
"Conversions": 326,
"Spend": 64.7100853845,
"affiliate": "Y_New Cars_4713",
"revenue": "",
"advertiser": "Umbrella",
"spend": 64.71,
"profit": -64.71,
"cpc": 0.05,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - New Cars - 7259_1875",
"affiliateId": "7259",
"Clicks": 1568,
"Conversions": 173,
"Spend": 51.5844874121,
"affiliate": "Y_New Cars_7259",
"revenue": "",
"advertiser": "Umbrella",
"spend": 51.58,
"profit": -51.58,
"cpc": 0.03,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - Destination - 7221_2068",
"affiliateId": "7221",
"Clicks": 75,
"Conversions": 0,
"Spend": 4.9945735649,
"affiliate": "Y_Destination_7221",
"revenue": 1.5,
"advertiser": "L-health",
"spend": 4.99,
"profit": -3.17,
"profitMargin": "-212",
"cpc": 0.06,
"rpc": 0.02,
"rpa": ""
}, {
"date": "2018-10-09",
"Campaign_Name": "test - Product - 7243_1791",
"affiliateId": "7243",
"Clicks": 36,
"Conversions": 0,
"Spend": 1.201965495,
"affiliate": "Y_Product_7243",
"revenue": 0.07,
"advertiser": "Product Tubs",
"spend": 1.2,
"profit": -1.13,
"profitMargin": "-1617",
"cpc": 0.03,
"rpc": 0,
"rpa": ""
}, {
"date": "2018-10-09",
"Campaign_Name": "test - Homewares - 7269_2163",
"affiliateId": "7269",
"Clicks": 11,
"Conversions": 0,
"Spend": 0.5186665021,
"affiliate": "Y_Homewares_7269",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 0.64,
"profit": -0.64,
"cpc": 0.05,
"rpc": 0,
"rpa": ""
}]
const columnHeaderMap = {
Date: "date",
AffiliateId: "affiliateId",
Spend: "spend",
Revenue: "revenue",
CPC: "cpc",
RPC: "rpc",
RPA: "rpa",
Profit: "profit",
PM: "profitMargin",
Campaign: "Campaign_Name",
Affiliate: "affiliate"
};
const headers = Object.keys(columnHeaderMap);
const columns = headers.map(header => columnHeaderMap[header]);
const getHeaderWithColumn = column => {
for (let header in columnHeaderMap) {
if (columnHeaderMap[header] === column) {
return header;
}
}
};
// // setup the area for the table
d3.selectAll('table').data([0]).enter().append('table');
var table1 = d3.select('#table');
table1.selectAll('thead').data([0]).enter().append('thead');
var thead = table1.select('thead');
table1.selectAll('tbody').data([0]).enter().append('tbody');
var tbody = table1.select('tbody');
var pmColorScale = d3.scaleThreshold()
.domain([0, 20])
.range(['red', '#FDE541', 'green']);
// // append the header row
thead.append('tr')
.selectAll('th')
.data(headers)
.enter()
.append('th')
.text(function (column) {
return column;
})
.on('click', function (d) {
thead.attr('class', 'header');
const columnName = columnHeaderMap[d];
if (sortAscending) {
rows.sort((a, b) => {
if (d === 'PM') {
if (isNaN(a.profitMargin)) {
return a.profitMargin == 0;
}
if (isNaN(b.profitMargin)) {
return b.profitMargin == 0;
}
a.profitMargin = Number.parseFloat(a.profitMargin);
b.profitMargin = Number.parseFloat(b.profitMargin);
// parse the string into a float
// then do the sort calc
}
return b[columnHeaderMap[d]] < a[columnHeaderMap[d]] ? 1 : -1;
});
sortAscending = false;
} else {
rows.sort((a, b) => {
if (d === 'PM') {
if (isNaN(a.profitMargin)) {
return a.profitMargin == 0;
}
if (isNaN(b.profitMargin)) {
return b.profitMargin == 0;
}
a.profitMargin = Number.parseFloat(a.profitMargin);
b.profitMargin = Number.parseFloat(b.profitMargin);
// parse the string into a float
// then do the sort calc
}
return b[columnHeaderMap[d]] > a[columnHeaderMap[d]] ? 1 : -1;
});
sortAscending = true;
}
});
// // create a row for each object in the data
var rows = tbody.selectAll('tr')
.data(merged)
.enter()
.append('tr');
// // create a cell in each row for each column
var cells = rows.selectAll('td')
.data(function (row) {
return columns.map(function (column) {
return {
column: getHeaderWithColumn(column),
value: row[column],
};
});
})
.enter()
.append('td')
.style("color", function (d) {
if (d.column === 'PM') {
return pmColorScale(d.value);
}
if (d.column === 'Profit') {
if (d.value < 0) {
return "red";
}
}
}).html(function (d) {
percentFormatter = d3.format(".0%");
dollarFormatter = d3.format("$,");
if (d.column === 'PM') {
if (!isNaN(d.value)) {
if (isNaN(d.value)) {
d.value === Number.parseInt(0);
}
return percentFormatter(d.value / 100);
}
}
if (d.column === 'Spend' || d.column === 'Revenue' || d.column === 'CPC' || d.column === 'RPC' || d.column === 'RPA' || d.column === 'Profit') {
if (!isNaN(d.value)) {
return dollarFormatter(d.value);
}
}
return d.value;
});
function sort(a, b) {
if (typeof a == "string") {
var parseA = format.parse(a);
if (parseA) {
var dateA = parseA.getDate();
var dateB = format.parse(b).getDate();
return dateA > dateB ? 1 : dateA == dateB ? 0 : -1;
} else
return a.localeCompare(b);
} else if (typeof a == "number") {
return a > b ? 1 : a == b ? 0 : -1;
} else if (typeof a == "boolean") {
return b ? 1 : a ? -1 : 0;
}
}
// Time to make the summary
// // This is a subtotal reducer so each id has its total
const summary = merged.reduce(function (val, acc) {
if (!val[acc.affiliateId]) val[acc.affiliateId] = {
affiliateId: acc.affiliateId,
Spend: 0,
revenue: 0,
profit: 0,
profitMargin: 0,
Clicks: 0,
Conversions: 0
};
val[acc.affiliateId].Clicks += Number.parseFloat(acc.Clicks);
val[acc.affiliateId].Conversions += Number.parseFloat(acc.Conversions);
val[acc.affiliateId].Spend += Number.parseFloat(acc.Spend);
val[acc.affiliateId].revenue += Number.parseFloat(acc.revenue);
val[acc.affiliateId].profit += Number.parseFloat(acc.profit);
val[acc.affiliateId].Campaign_Name = acc.Campaign_Name;
val[acc.affiliateId].affiliate = acc.affiliate;
val[acc.affiliateId].advertiser = acc.advertiser;
return val;
}, {});
// console.log(summary); // returns the array with the accumulators and ids as keys, after which I then I set to an array to look like typical JSON
const summaryArr = [];
for (var entry in summary) {
// console.log(sum[entry]);
summaryArr.push(summary[entry]);
}
}
}
答案 0 :(得分:1)
如果首先计算摘要并将其与嵌套数据集成,则可以使用d3.nest
和一些数据争执来进行此操作。如果您制作了一个用于添加td
元素的函数(也就是将现有代码变成一个函数),也会更容易:
function addCells ( selection ) {
// create a cell in each row for each column
selection.selectAll('td')
.data(function(row) {
return columns.map(function(column) {
return {
column: getHeaderWithColumn(column),
value: row[column],
};
});
})
.enter()
.append('td')
.style("color", function(d) {
if (d.column === 'PM') {
return pmColorScale(d.value);
}
if (d.column === 'Profit') {
if (d.value < 0) {
return "red";
}
}
}).html(function(d) {
percentFormatter = d3.format(".0%");
dollarFormatter = d3.format("$,");
if (d.column === 'PM') {
if (!isNaN(d.value)) {
if (isNaN(d.value)) {
d.value === Number.parseInt(0);
}
return percentFormatter(d.value / 100);
}
}
if (d.column === 'Spend' || d.column === 'Revenue' || d.column === 'CPC' || d.column === 'RPC' || d.column === 'RPA' || d.column === 'Profit') {
if (!isNaN(d.value)) {
return dollarFormatter(d.value);
}
}
return d.value;
});
}
表可以包含任意数量的tbody
元素,因此我们可以利用它,并为代表会员的每行添加一个单独的tbody
。
首先,计算摘要:
const summary = merged.reduce(function(val, acc) {
if (!val[acc.affiliateId]) val[acc.affiliateId] = {
affiliateId: acc.affiliateId,
Spend: 0,
revenue: 0,
profit: 0,
profitMargin: 0,
Clicks: 0,
Conversions: 0
};
val[acc.affiliateId].Clicks += Number.parseFloat(acc.Clicks);
val[acc.affiliateId].Conversions += Number.parseFloat(acc.Conversions);
val[acc.affiliateId].Spend += Number.parseFloat(acc.Spend);
val[acc.affiliateId].revenue += Number.parseFloat(acc.revenue);
val[acc.affiliateId].profit += Number.parseFloat(acc.profit);
val[acc.affiliateId].Campaign_Name = acc.Campaign_Name;
val[acc.affiliateId].affiliate = acc.affiliate;
val[acc.affiliateId].advertiser = acc.advertiser;
return val;
}, {});
使用affiliateId
作为键嵌套数据,并将summary
数据集成到嵌套数据中:
const nested = d3.nest()
.key( d => d.affiliateId )
.entries(merged)
.map( d => { d.header = summary[d.key]; return d } );
nested
现在是一个数组,其中的条目如下所示:
{key: "6480",
values: [Array], // rows with affiliateId 6480
header: Object // collated data on 6480 from `summary`
}
将其绑定到表中,并为每个条目添加一个tbody
:
var tbody = table1.selectAll('tbody')
.data(nested)
.enter()
.append('tbody');
通过从绑定数据中获取标头,为摘要数据添加行。请注意,d3需要数据位于数组中,因此我们将标头数据作为单元素数组返回。给该行一个类,以区别于接下来要添加的每月数据。
var summaryRow = tbody
.selectAll('tr.summary')
.data(function(d) { return [d.header] })
.enter()
.append('tr')
.classed('summary',true)
为该行添加td
元素:
addCells(summary)
现在,您可以对d3.nest
放入d.values
中的每月数据集的行进行相同的操作。添加行,然后将单元格添加到行:
var rows = tbody.selectAll('tr.entry')
.data(d => {
return d.values
})
.enter()
.append('tr')
.classed('entry', true)
addCells(rows);
带有一些虚假数据的完整演示:
function go() {
const merged = [{
"date": "2018-10-09",
"Campaign_Name": "Foo - 6480_1925",
"affiliateId": "6480",
"Clicks": 6,
"Conversions": 0,
"Spend": 0.5019512028,
"affiliate": "Y_Foo_6480",
"revenue": 58.22,
"advertiser": "sky",
"spend": 0.5,
"profit": 57.72,
"profitMargin": "99",
"cpc": 0.08,
"rpc": 9.7,
"rpa": ""
}, {
"date": "2018-09-09",
"Campaign_Name": "Foo - 6480_1925",
"affiliateId": "6480",
"Clicks": 6,
"Conversions": 0,
"Spend": 0.5019512028,
"affiliate": "Y_Foo_6480",
"revenue": 58.22,
"advertiser": "sky",
"spend": 0.5,
"profit": 57.72,
"profitMargin": "99",
"cpc": 0.08,
"rpc": 9.7,
"rpa": ""
}, {
"date": "2018-08-09",
"Campaign_Name": "Foo - 6480_1925",
"affiliateId": "6480",
"Clicks": 6,
"Conversions": 0,
"Spend": 0.5019512028,
"affiliate": "Y_Foo_6480",
"revenue": 58.22,
"advertiser": "sky",
"spend": 0.5,
"profit": 57.72,
"profitMargin": "99",
"cpc": 0.08,
"rpc": 9.7,
"rpa": ""
}, {
"date": "2018-07-09",
"Campaign_Name": "Foo - 6480_1925",
"affiliateId": "6480",
"Clicks": 6,
"Conversions": 0,
"Spend": 0.5019512028,
"affiliate": "Y_Foo_6480",
"revenue": 58.22,
"advertiser": "sky",
"spend": 0.5,
"profit": 57.72,
"profitMargin": "99",
"cpc": 0.08,
"rpc": 9.7,
"rpa": ""
}, {
"date": "2018-10-09",
"Campaign_Name": "Bar Mutual - 7157_2020",
"affiliateId": "7157",
"Clicks": 583,
"Conversions": 0,
"Spend": 166.0008698087,
"affiliate": "Y_GetStuff_7191",
"revenue": 2.22,
"advertiser": "Bar Mutual Insurance",
"spend": 166,
"profit": -163.78,
"profitMargin": "-7378",
"cpc": 0.28,
"rpc": 0,
"rpa": ""
}, {
"date": "2018-09-09",
"Campaign_Name": "Bar Mutual - 7157_2020",
"affiliateId": "7157",
"Clicks": 1,
"Conversions": 0,
"Spend": 0.0108815003,
"affiliate": "Y_GetStuff_7191",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 0.01,
"profit": -0.01,
"cpc": 0.01,
"rpc": 0,
"rpa": ""
}, {
"date": "2018-08-09",
"Campaign_Name": "Bar Mutual - 7157_2020",
"affiliateId": "7157",
"Clicks": 6,
"Conversions": 0,
"Spend": 1.3499999642,
"affiliate": "Y_GetStuff_7191",
"revenue": 0.36,
"advertiser": "Art",
"spend": 1.35,
"profit": -0.99,
"profitMargin": "-275",
"cpc": 0.22,
"rpc": 0.06,
"rpa": ""
}, {
"date": "2018-07-09",
"Campaign_Name": "Bar Mutual - 7157_2020",
"affiliateId": "7157",
"Clicks": 199,
"Conversions": 0,
"Spend": 10.2255493868,
"affiliate": "Y_GetStuff_7191",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 10.23,
"profit": -10.23,
"cpc": 0.06,
"rpc": 0,
"rpa": ""
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - 4735_2092",
"affiliateId": "4735",
"Clicks": 200,
"Conversions": 34,
"Spend": 59.1212777495,
"affiliate": "Y_Mobile-3B_OMNewCar_4735",
"revenue": 20.1,
"advertiser": "Acme, Inc. ",
"spend": 59.12,
"profit": -39.02,
"profitMargin": "-194",
"cpc": 0.3,
"rpc": 0.1,
"rpa": 0.59
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - 6586_2092",
"affiliateId": "6586",
"Clicks": 472,
"Conversions": 79,
"Spend": 61.0002093334,
"affiliate": "Y_New Cars_6586",
"revenue": 0.75,
"advertiser": "Acme, Inc. ",
"spend": 61,
"profit": -60.25,
"profitMargin": "-8033",
"cpc": 0.13,
"rpc": 0,
"rpa": 0.01
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - 6618_2092",
"affiliateId": "6618",
"Clicks": 2,
"Conversions": 1,
"Spend": 0.2018772066,
"affiliate": "Y_New Cars_6618",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 0.2,
"profit": -0.2,
"cpc": 0.1,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - 7247_1773",
"affiliateId": "7247",
"Clicks": 76,
"Conversions": 7,
"Spend": 13.9912065665,
"affiliate": "Y_New Cars_7247",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 13.99,
"profit": -13.99,
"cpc": 0.18,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - NSConvLAL - 6594_2092",
"affiliateId": "6594",
"Clicks": 905,
"Conversions": 264,
"Spend": 293.5172631741,
"affiliate": "Y_New Cars_6594",
"revenue": 1.72,
"advertiser": "Acme, Inc. ",
"spend": 293.64,
"profit": -291.8,
"profitMargin": "-16965",
"cpc": 0.32,
"rpc": 0,
"rpa": 0.01
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - NSConvLAL - 7251_2092",
"affiliateId": "7251",
"Clicks": 202,
"Conversions": 1,
"Spend": 64.9944748056,
"affiliate": "Y_New Cars_7251",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 64.99,
"profit": -64.99,
"cpc": 0.26,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - NS - New Cars - Span/Eng - 7165_1773",
"affiliateId": "7165",
"Clicks": 891,
"Conversions": 49,
"Spend": 74.5347691271,
"affiliate": "Y_New Cars_7165",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 74.53,
"profit": -74.53,
"cpc": 0.08,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - New Cars - 4713_1875",
"affiliateId": "4713",
"Clicks": 1084,
"Conversions": 326,
"Spend": 64.7100853845,
"affiliate": "Y_New Cars_4713",
"revenue": "",
"advertiser": "Umbrella",
"spend": 64.71,
"profit": -64.71,
"cpc": 0.05,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - New Cars - 7259_1875",
"affiliateId": "7259",
"Clicks": 1568,
"Conversions": 173,
"Spend": 51.5844874121,
"affiliate": "Y_New Cars_7259",
"revenue": "",
"advertiser": "Umbrella",
"spend": 51.58,
"profit": -51.58,
"cpc": 0.03,
"rpc": 0,
"rpa": 0
}, {
"date": "2018-10-09",
"Campaign_Name": "test - Destination - 7221_2068",
"affiliateId": "7221",
"Clicks": 75,
"Conversions": 0,
"Spend": 4.9945735649,
"affiliate": "Y_Destination_7221",
"revenue": 1.5,
"advertiser": "L-health",
"spend": 4.99,
"profit": -3.17,
"profitMargin": "-212",
"cpc": 0.06,
"rpc": 0.02,
"rpa": ""
}, {
"date": "2018-10-09",
"Campaign_Name": "test - Product - 7243_1791",
"affiliateId": "7243",
"Clicks": 36,
"Conversions": 0,
"Spend": 1.201965495,
"affiliate": "Y_Product_7243",
"revenue": 0.07,
"advertiser": "Product Tubs",
"spend": 1.2,
"profit": -1.13,
"profitMargin": "-1617",
"cpc": 0.03,
"rpc": 0,
"rpa": ""
}, {
"date": "2018-10-09",
"Campaign_Name": "test - Homewares - 7269_2163",
"affiliateId": "7269",
"Clicks": 11,
"Conversions": 0,
"Spend": 0.5186665021,
"affiliate": "Y_Homewares_7269",
"revenue": "",
"advertiser": "Acme, Inc. ",
"spend": 0.64,
"profit": -0.64,
"cpc": 0.05,
"rpc": 0,
"rpa": ""
}]
const columnHeaderMap = {
Date: "date",
AffiliateId: "affiliateId",
Spend: "spend",
Revenue: "revenue",
CPC: "cpc",
RPC: "rpc",
RPA: "rpa",
Profit: "profit",
PM: "profitMargin",
Campaign: "Campaign_Name",
Affiliate: "affiliate"
};
const headers = Object.keys(columnHeaderMap);
const columns = headers.map(header => columnHeaderMap[header]);
const getHeaderWithColumn = column => {
for (let header in columnHeaderMap) {
if (columnHeaderMap[header] === column) {
return header;
}
}
};
var pmColorScale = d3.scaleThreshold()
.domain([0, 20])
.range(['red', '#FDE541', 'green']);
// // setup the area for the table
// d3.selectAll('table').data([0]).enter().append('table');
var table1 = d3.select('#table');
table1.selectAll('thead').data([0]).enter().append('thead');
var thead = table1.select('thead');
// // append the header row
thead.append('tr')
.selectAll('th')
.data(headers)
.enter()
.append('th')
.text(function(column) {
return column;
})
.on('click', function(d) {
thead.attr('class', 'header');
const columnName = columnHeaderMap[d];
if (sortAscending) {
rows.sort((a, b) => {
if (d === 'PM') {
if (isNaN(a.profitMargin)) {
return a.profitMargin == 0;
}
if (isNaN(b.profitMargin)) {
return b.profitMargin == 0;
}
a.profitMargin = Number.parseFloat(a.profitMargin);
b.profitMargin = Number.parseFloat(b.profitMargin);
// parse the string into a float
// then do the sort calc
}
return b[columnHeaderMap[d]] < a[columnHeaderMap[d]] ? 1 : -1;
});
sortAscending = false;
} else {
rows.sort((a, b) => {
if (d === 'PM') {
if (isNaN(a.profitMargin)) {
return a.profitMargin == 0;
}
if (isNaN(b.profitMargin)) {
return b.profitMargin == 0;
}
a.profitMargin = Number.parseFloat(a.profitMargin);
b.profitMargin = Number.parseFloat(b.profitMargin);
// parse the string into a float
// then do the sort calc
}
return b[columnHeaderMap[d]] > a[columnHeaderMap[d]] ? 1 : -1;
});
sortAscending = true;
}
});
// Time to make the summary
// // This is a subtotal reducer so each id has its total
const summary = merged.reduce(function(val, acc) {
if (!val[acc.affiliateId]) val[acc.affiliateId] = {
affiliateId: acc.affiliateId,
Spend: 0,
revenue: 0,
profit: 0,
profitMargin: 0,
Clicks: 0,
Conversions: 0
};
val[acc.affiliateId].Clicks += Number.parseFloat(acc.Clicks);
val[acc.affiliateId].Conversions += Number.parseFloat(acc.Conversions);
val[acc.affiliateId].Spend += Number.parseFloat(acc.Spend);
val[acc.affiliateId].revenue += Number.parseFloat(acc.revenue);
val[acc.affiliateId].profit += Number.parseFloat(acc.profit);
val[acc.affiliateId].Campaign_Name = acc.Campaign_Name;
val[acc.affiliateId].affiliate = acc.affiliate;
val[acc.affiliateId].advertiser = acc.advertiser;
return val;
}, {});
const nested = d3.nest()
.key( d => d.affiliateId )
.entries(merged)
.map( d => { d.header = summary[d.key]; return d } );
var tbody = table1.selectAll('tbody')
.data(nested)
.enter()
.append('tbody');
var summaryRow = tbody
.selectAll('tr.summary')
.data(d => [d.header])
.enter()
.append('tr')
.classed('summary',true)
addCells(summaryRow)
// create a row for each object in the data
var rows = tbody.selectAll('tr.entry')
.data(d => {
return d.values
})
.enter()
.append('tr')
.classed('entry', true)
addCells(rows);
function addCells ( selection ) {
// create a cell in each row for each column
selection.selectAll('td')
.data(function(row) {
return columns.map(function(column) {
return {
column: getHeaderWithColumn(column),
value: row[column],
};
});
})
.enter()
.append('td')
.style("color", function(d) {
if (d.column === 'PM') {
return pmColorScale(d.value);
}
if (d.column === 'Profit') {
if (d.value < 0) {
return "red";
}
}
}).html(function(d) {
percentFormatter = d3.format(".0%");
dollarFormatter = d3.format("$,");
if (d.column === 'PM') {
if (!isNaN(d.value)) {
if (isNaN(d.value)) {
d.value === Number.parseInt(0);
}
return percentFormatter(d.value / 100);
}
}
if (d.column === 'Spend' || d.column === 'Revenue' || d.column === 'CPC' || d.column === 'RPC' || d.column === 'RPA' || d.column === 'Profit') {
if (!isNaN(d.value)) {
return dollarFormatter(d.value);
}
}
return d.value;
});
}
function sort(a, b) {
if (typeof a == "string") {
var parseA = format.parse(a);
if (parseA) {
var dateA = parseA.getDate();
var dateB = format.parse(b).getDate();
return dateA > dateB ? 1 : dateA == dateB ? 0 : -1;
} else
return a.localeCompare(b);
} else if (typeof a == "number") {
return a > b ? 1 : a == b ? 0 : -1;
} else if (typeof a == "boolean") {
return b ? 1 : a ? -1 : 0;
}
}
}
window.onload = go;
.summary td {
font-weight: bold;
background-color: aliceblue;
}
<script src="http://d3js.org/d3.v5.js"></script>
<table id="table"></table>