I'm new to D3 and I am trying to display a simple d3 bar chart that changes which data attribute it is visualizing based on a dropdown menu - the data remains the same and I am displaying the same labels (x-axis) with each dropdown selection, just the labels should transition/change their ordering and the bar values should transition/change based on which attribute they are showing.
When the dropdown menu changes however, the transition (update) selection isn't getting called - it is only called when the chart loads for the first time. Therefore, based on the code, the y-Axis is changing its numerical values, but the heights always remain the same as they are initiated so the bars don't animate at all despite the labels changing.
updateChart(menuSelection) { // called when dropdown selection changes, and initially upon page load with default menuSelection
// I sort the data by the attribute of the dropdown item selected
this.myData.sort(function(a,b){
if(menuSelection == 1) return b.count - a.count;
else if(menuSelection == 2) return b.positiveCount - a.positiveCount;
else return b.negativeCount - a.negativeCount;
});
var m = this.myData.length;
var svg = d3.select(".chart"),
margin = {top: 40, right: 25, bottom: 40, left: 25},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var g = svg.select("g.rectGroup").attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("class", "rectGroup");
if(g.empty()) {
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")").attr("class", "rectGroup");
}
var x = d3.scaleBand()
.domain(myData.map(function(d) { return d._id; }))
.range([0, width])
.padding(0.08);
var yMax = d3.max(this.myData, function(d) {
if(this.menuSelection == 1) return d.count;
else if(this.menuSelection == 2) return d.positiveCount;
else return d.negativeCount;
});
var y = d3.scaleLinear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.axisBottom()
.scale(x);
var yAxis = d3.axisLeft()
.scale(y);
var yz = getHeights(m, menuSelection, this.myData); // ARRAY OF HEIGHTS DEPENDING ON MENU DROP DOWN SELECTION
var bars = g.selectAll(".bar")
.data(this.myData, function(d) {
return d._id; // KEY IS ITEM NAME FOR OBJECT CONSTANCY; ALL ITEMS ARE DISPLAYED REGARDLESS OF ATTRIBUTE SELECTED, BUT ORDER/VALUES CHANGE FOR EACH ITEM
})
.enter().append("rect")
.attr("class", "bar")
.attr("height", 0)
.attr("y", height);
bars.transition().duration(700)
.attr("x", function(d) { return x(d._id); })
.attr("width", x.bandwidth())
.attr("y", function(d, i) { return y(yz[i])})
.attr("height", function(d, i) {
return height - y(yz[i])
});
bars.exit().remove();
svg.selectAll(".axis").remove();
var height_to_use = +svg.attr("height") - margin.bottom
var xAxis_g = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + margin.left + "," + height_to_use + ")")
.call(xAxis)
.selectAll(".tick text")
.call(wrap, x.bandwidth());
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
function getHeights(m, menuSelection, data) {
var values = [];
for (var i = 0; i < m; ++i) {
if(menuSelection == 1) {
values[i] = data[i].count;
} else if(menuSelection == 2) {
values[i] = data[i].positiveCount;
} else {
values[i] = data[i].negativeCount;
}
}
return values;
}
}
Actually, you don't have an update selection in your code.
For having an update selection, break up your "enter" selection, like this:
//this is the update selection
var bars = g.selectAll(".bar")
.data(data, function(d) {
return d._id;
});
//and the remainder is the enter selection
bars.enter().append("rect")
.attr("class", "bar")
.attr("height", 0)
.attr("y", height);
Also, it's worth mentioning that, since you don't have an update selection in your code, this...
bars.exit().remove();
... is useless.
Related
I'm trying to follow this guide, but implementing my own data:
https://www.d3-graph-gallery.com/graph/stackedarea_basic.html
Here is my function
getStackedAreaChart: function(pod) {
//eval is sligtly heavily used here.
var cssName = ".stackedareachart-" + pod;
var podData = eval("this.StackedAreaChartData" + pod);
var ListName = eval("this.List" + pod);
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 30, left: 60 },
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3
.select(cssName)
.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 + ")");
var sumstat = d3
.nest()
.key(function(d) {
return d.time;
})
.entries(podData);
console.log(sumstat);
// Stack the data: each group will be represented on top of each other
var mygroups = ListName; // list of group names
var mygroup = []; // list of group names
for (let i = 1; i <= mygroups.length; i++) {
mygroup.push(i);
}
console.log(mygroups);
console.log(mygroup);
var stackedData = d3
.stack()
.keys(mygroup)
.value(function(d, key) {
return d.values[key].interactionCount;
})(sumstat);
// Add X axis --> it is a date format
var x = d3
.scaleLinear()
.domain(
d3.extent(podData, function(d) {
return d.time;
})
)
.range([0, width]);
svg
.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5));
// Add Y axis
var y = d3
.scaleLinear()
.domain([
0,
d3.max(podData, function(d) {
return +d.interactionCount;
})
])
.range([height, 0]);
svg.append("g").call(d3.axisLeft(y));
// color palette
var color = d3
.scaleOrdinal()
.domain(mygroups)
.range([
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33",
"#a65628",
"#f781bf",
"#999999"
]);
// Show the areas
svg
.selectAll("mylayers")
.data(stackedData)
.enter()
.append("path")
.style("fill", function(d) {
name = mygroups[d.key - 1];
return color(name);
})
.attr(
"d",
d3
.area()
.x(function(d, i) {
return x(d.data.key);
})
.y0(function(d) {
return y(d[0]);
})
.y1(function(d) {
return y(d[1]);
})
);
}
}
Here is where I get the error:
TypeError: Cannot read property 'interactionCount' of undefined
var stackedData = d3
.stack()
.keys(mygroups)
.value(function(d, key) {
return d.values[key].interactionCount;
})(sumstat);
For some reason if I make the list mygroup have one less element in the array, I don't get this error. BUT, my chart doesn't come out looking right.
I've followed the guide word for word line by line and I have no problems replicating the chart. But, when using my own data, I run into issues. Here is the json data:
[{"interactionCount":0,"time":951,"pod":"POD2","client":"C1"},{"interactionCount":6,"time":951,"pod":"POD2","client":"C2"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C3"},{"interactionCount":14,"time":951,"pod":"POD2","client":"C4"},{"interactionCount":44,"time":951,"pod":"POD2","client":"C5"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C6"},{"interactionCount":8,"time":951,"pod":"POD2","client":"C7"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C8"},{"interactionCount":5,"time":951,"pod":"POD2","client":"C9"},{"interactionCount":2,"time":951,"pod":"POD2","client":"C10"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C11"},{"interactionCount":13,"time":951,"pod":"POD2","client":"C12"},{"interactionCount":6,"time":951,"pod":"POD2","client":"C13"},{"interactionCount":0,"time":951,"pod":"POD2","client":"C14"},{"interactionCount":6,"time":951,"pod":"POD2","client":"C15"}]
I was thinking maybe the error was when interactionCount was 0. This is not a problem. I did test this out.
Although I'm following the guide line by line. What am I doing wrong to receive the error?
NOTE my data is json data. The user uses CSV data. Could this be my problem?
I try to combine multiple mouse events, which i took from https://www.d3-graph-gallery.com/graph/barplot_stacked_hover.html AND https://www.d3-graph-gallery.com/graph/barplot_stacked_highlight.html .
Unfortunately, the mousemove funtion doesn't work. I want to have the "tooltip" following my mouse as shown in the first example.
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 20,
left: 50
},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Parse the Data
d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_stackedXL.csv").then(function(data) {
// List of subgroups = header of the csv files = soil condition here
var subgroups = data.columns.slice(1)
// List of groups = species here = value of the first column called group -> I show them on the X axis
var groups = d3.map(data, function(d) {
return (d.group)
}).keys()
// Add X axis
var x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSizeOuter(0));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 120])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(d3.schemeSet2);
//stack the data? --> stack per subgroup
var stackedData = d3.stack()
.keys(subgroups)
(data)
// ----------------
// Highlight a specific subgroup when hovered
// ----------------
// ----------------
// Create a tooltip
// ----------------
var tooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("padding", "1px")
// What happens when user hover a bar
var mouseover = function(d) {
// what subgroup are we hovering?
var subgroupName = d3.select(this.parentNode).datum().key; // This was the tricky part
var subgroupValue = d.data[subgroupName];
// Reduce opacity of all rect to 0.2
d3.selectAll(".myRect").style("opacity", 0.2)
// Highlight all rects of this subgroup with opacity 0.8. It is possible to select them since they have a specific class = their name.
d3.selectAll("." + subgroupName)
.style("opacity", 1)
tooltip
.html("subgroup: " + subgroupName + "<br>" + "Value: " + subgroupValue)
.style("opacity", 1)
}
var mousemove = function(d) {
tooltip
.style("left", (d3.mouse(this)[0] + 90) + "px") // It is important to put the +90: other wise the tooltip is exactly where the point is an it creates a weird effect
.style("top", (d3.mouse(this)[1]) + "px")
}
// When user do not hover anymore
var mouseleave = function(d) {
// Back to normal opacity: 0.8
d3.selectAll(".myRect")
.style("opacity", 0.8)
tooltip
.style("opacity", 0)
}
// Show the bars
svg.append("g")
.selectAll("g")
// Enter in the stack data = loop key per key = group per group
.data(stackedData)
.enter().append("g")
.attr("fill", function(d) {
return color(d.key);
})
.attr("class", function(d) {
return "myRect " + d.key
}) // Add a class to each subgroup: their name
.selectAll("rect")
// enter a second time = loop subgroup per subgroup to add all rectangles
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.data.group);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("height", function(d) {
return y(d[0]) - y(d[1]);
})
.attr("width", x.bandwidth())
.attr("stroke", "grey")
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
})
<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js "></script>
</head>
<body>
<div id="my_dataviz"></div>
</body>
I have created a D3 chart which I would like to update after the user clicks on one of the radio buttons. The value of the button isn't part of the data but an update of the time range along the X axis which should update as well as Y axis data based on the newly selected timeRange value (starting point is 36 hours from "now"). I can tell from console.log statements that the timeRange is updating when the user clicks on a radio button but can't figure out what I need to do to get that chart updated (the change() function is what I am trying to use to make this happen.
Here is a fiddle: https://jsfiddle.net/dtepdc/L1qf0bvk/
Here is my code:
const dataset = [
{
start_date: "2019-01-27T14:30:40",
end_date: "2019-01-27T16:32:25",
elapsed_time: 130,
coNum:"CO19044"
},
{
start_date: "2019-01-27T03:05:40",
end_date: "2019-01-27T03:32:25",
elapsed_date: 189,
coNum:"CO12904"
},
{
start_date: "2019-01-26T22:15:40",
end_date: "2019-01-26T23:32:25",
elapsed_time: 89,
coNum:"CO18345"
},
{
start_date: "2019-01-26T07:00:40",
end_date: "2019-01-26T07:40:25",
elapsed_time: 89,
coNum:"CO12005"
}
];
const coNumW = window.innerWidth,
coNumH = window.innerHeight,
margin = {top: coNumH * 0.15, right: coNumW * 0.05, bottom: coNumH * 0.12, left: coNumW * 0.12},
w = coNumW - margin.left - margin.right,
h = coNumH - margin.top - margin.bottom;
const xSc = d3.scaleTime().range([0, w]),
ySc = d3.scaleBand().range([h, 0]),
xAxis = d3.axisBottom(xSc),
yAxis = d3.axisLeft(ySc),
filtered = [],
dateFormat = d3.timeFormat("%Y-%m-%d %I:%M %p");
const svg = d3.select("body")
.append("svg")
.attr("width", coNumW)
.attr("height", coNumH)
.append("g").classed("no-select", true)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let radio = d3.select('input[name="options"]:checked').property("value");
let timeRange = radio;
let start = moment().subtract(timeRange, 'hours').format('LLL');
const end = moment().format('LLL');
timeRange = this.value;
dataset.forEach(function(d, i) {
console.log('forEach timeRange: ', timeRange);
d.start_date = new Date(d.start_date);
d.end_date = new Date(d.end_date);
if (d.start_date >= new Date(start) && d.end_date <= new Date(end)) {
filtered.push(d);
}
});
xSc.domain([new Date(end), new Date(start)])
.range([0, w]);
ySc.domain(filtered.map(d => d.coNum)).padding(0.1);
console.log('xSc & ySc timeRange: ', timeRange)
svg.append("g")
.attr("class", "x Axis")
.attr("transform", "translate(0, " + h + ")")
.call(xAxis)
svg.append("g")
.attr("class", "y Axis")
.call(yAxis);
const tasks = svg.append("g").attr("class", "dataCont")
.selectAll("g")
.data(filtered)
.enter()
.append("g")
.on("mouseenter", showData);
tasks.append("rect")
.attr("x", function(d) {
return xSc(d.start_date) + 2; // + 2 is for padding
})
.attr("y", function(d) {
return ySc(d.coNum);
})
.attr("width", function(d) {
return xSc(d.start_date) - xSc(d.end_date) - 2;
})
.attr("height", function(d) {
return ySc.bandwidth();
})
.attr("fill", "green");
d3.selectAll("input")
.on("change", change);
function change() {
timeRange = this.value;
dataset.forEach(function(d, i) {
console.log('forEach timeRange: ', timeRange);
d.start_date = new Date(d.start_date);
d.end_date = new Date(d.end_date);
if (d.start_date >= new Date(start) && d.end_date <= new Date(end)) {
filtered.push(d);
}
});
xSc.domain([new Date(end), new Date(start)])
.range([0, w]);
ySc.domain(filtered.map(d => d.coNum)).padding(0.1);
console.log('xSc & ySc timeRange: ', timeRange)
svg.append("g")
.attr("class", "x Axis")
.attr("transform", "translate(0, " + h + ")")
.call(xAxis)
svg.append("g")
.attr("class", "y Axis")
.call(yAxis);
const tasks = svg.append("g").attr("class", "dataCont")
.selectAll("g")
.data(filtered)
.enter()
.append("g")
.on("mouseenter", showData);
tasks.append("rect")
.attr("x", function(d) {
return xSc(d.start_date) + 2; // + 2 is for padding
})
.attr("y", function(d) {
return ySc(d.coNum);
})
.attr("width", function(d) {
return xSc(d.start_date) - xSc(d.end_date) - 2;
})
.attr("height", function(d) {
return ySc.bandwidth();
})
.attr("fill", "green");
}
function showData(d) {
const dur = (d.end_date - d.start_date)/3600000;
console.log("-" + d.coNum + "- start_date: " + dateFormat(d.start_date) + " || end_date: " + dateFormat(d.end_date))
}
I've done something similar in a project. I think the main thing you are missing is that when you change the data you need to remove the current chart and draw a new one. You may be able to make Angular do this for you, but I'm not sure how.
Personally, I would refactor the logic into something like drawChart(filteredData) and filterData(unfilteredData, HOW TO FILTER). The initial draw on page load would be a call to drawChart with whatever data you want (it could go through filterData(). Your change() function would take the HOW TO FILTER from the radio button, pass it to filterData(), then remove the chart, then call drawChart with the filtered data. This architecture makes every draw of the chart the same and if you need changes to how the chart it drawn you can do it one place.
Example of new change function
change(howToFilter){
var filteredData = filterData(dataset, howToFilter);
CODE TO REMOVE THE CHART
drawChart(filteredData);
}
With help from https://bl.ocks.org histogram example I try to create a histogram with JSON from AJAX.
It seems like my data is not suitable for the histogram() function.
My Data in dev tools (top = my data; bottom = bins from the histogram):
My data is not in histogram bins. The array objects are missing.
Here are the data from bl.ocks.org working example:
...and the bins from histogram from bl.ocks.org example:
You can see it clearly. In my experiment, the data is not in the bins. In the working example of bl.ocks.org you can see the additional objects as an array from index 1 to 13 in the histogram bins.
Here is my full source code:
$(function () {
var updateStatistic = function () {
var dateFrom = $('#date_from').val();
var dateTo = $('#date_to').val();
var parseDate = d3.timeParse('%Y-%m-%d %H:%M:%S'), formatCount = d3.format(',.0f');
var margin = {top: 10, right: 10, bottom: 20, left: 10},
width = 1800 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var dataset = [];
d3.json('/statistic-sizearchive/' + dateFrom + '/' + dateTo, function (data) {
dataset = data.sizeArchive;
dataset.columns = ['date'];
var datetimeFrom = parseDate(dataset[0].archive_time_sql);
var datetimeTo = parseDate(dataset[dataset.length - 1].archive_time_sql);
$(dataset).each(function (index, element) {
element.date = parseDate(element.archive_time_sql);
delete element.archive_time_sql;
});
console.log(dataset);
var x = d3.scaleTime()
.domain([datetimeFrom, datetimeTo])
.rangeRound([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var histogram = d3.histogram()
.value(function (d) {
return d.length;
})
.domain(x.domain())
.thresholds(x.ticks(d3.timeWeek));
var bins = histogram(dataset);
console.log(bins);
y.domain([0, d3.max(bins, function (d) {
return d.length;
})]);
/*
* ### SVG
*/
var svg = d3.select('#statistic_size_archive').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 + ")");
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
var bar = svg.selectAll(".bar")
.data(bins)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function (d) {
return "translate(" + x(d.x0) + "," + y(d.length) + ")";
})
;
bar.append("rect")
.attr("x", 1)
.attr("width", function (d) {
return x(d.x1) - x(d.x0); // x(d.x1) - x(d.x0) - 1
})
.attr("height", function (d) {
return height - y(d.length); // height - y(d.length)
});
bar.append("text")
.attr("dy", ".75em")
.attr("y", 0)
.attr("x", function (d) {
return (x(d.x1) - x(d.x0)) / 2;
})
.attr("text-anchor", "middle")
.text(function (d) {
return formatCount(d.length);
});
});
};
updateStatistic();
$('button#update_statistic').click(function () {
updateStatistic();
});
});
I do not see anything that I'm doing wrong.
Without your actual data, I'm not able to test this code... however, it appears that your histogram call function is returning the wrong value from the data. Instead of returning d.length, shouldn't the code be:
var histogram = d3.histogram()
.value(function (d) {
return d.date;
})
...
This way, the histogram will put each data point into a bin determined by its date?
I`m using a d3.js linechart with the following code:
var data = [{"age":"3","compositions":"1"},{"age":"5","compositions":"6"},{"age":"6","compositions":"5"},{"age":"7","compositions":"4"},{"age":"8","compositions":"9"},{"age":"9","compositions":"7"},{"age":"10","compositions":"16"},{"age":"11","compositions":"11"},{"age":"12","compositions":"11"},{"age":"13","compositions":"12"},{"age":"14","compositions":"19"},{"age":"15","compositions":"15"},{"age":"16","compositions":"30"},{"age":"17","compositions":"29"},{"age":"18","compositions":"21"},{"age":"19","compositions":"22"},{"age":"20","compositions":"29"},{"age":"21","compositions":"24"},{"age":"22","compositions":"28"},{"age":"23","compositions":"19"},{"age":"24","compositions":"13"},{"age":"25","compositions":"25"},{"age":"26","compositions":"36"},{"age":"27","compositions":"29"},{"age":"28","compositions":"23"},{"age":"29","compositions":"26"},{"age":"30","compositions":"24"},{"age":"31","compositions":"30"},{"age":"32","compositions":"33"},{"age":"33","compositions":"20"},{"age":"34","compositions":"9"},{"age":"35","compositions":"30"}]
var margin = {
top : 70,
right : 20,
bottom : 50,
left : 50
}, width = 460, height = 230;
var x = d3.scale.linear().range([0, width ]);
var y = d3.scale.linear().range([ height, 0 ]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left");
var line = d3.svg.line().x(function(d) {
return x(d.age);
}).y(function(d) {
return y(d.compositions);
});
var svg = d3
.select(".linechartAgePublicationsComposer")
.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 + ")");
data.forEach(function(d) {
d.age = d.age;
d.compositions = +d.compositions;
});
x.domain(d3.extent(data, function(d) {
return d.age;
}));
y.domain(d3.extent(data, function(d) {
return d.compositions;
}));
svg.append("g").attr("class", "x axis").attr("transform",
"translate(0," + height + ")").call(xAxis);
svg.append("g").attr("class", "y axis").call(yAxis).append("text")
.attr("transform", "rotate(-90)").attr("y", 6).attr("dy",
".71em").style("text-anchor", "end").text(
"Compositions");
svg.append("path").datum(data).attr("class", "line")
.attr("d", line);
when I execute it the line is out of the set range as in the picture and the y-axis has the wrong kind of direction.
Why is the line not within the range and goes from 9 to 10 only?
You should convert the age attribute to number, as you are doing with the composition attribute:
data.forEach(function(d) {
d.age = +d.age;
d.compositions = +d.compositions;
});
The d3.extent will return the minimum and maximum value in the given array using natural order. In your case you're not casting strings to numbers, so obviously the lowest string is "10" and the highest is "9". Thus, to fix the issue with the range, just cast the age to numbers as in the following example. I'm suggesting using parseInt
x.domain(d3.extent(data, function(d) {
return parseInt(d.age, 10);
}));