@cryptotaxi247 / netdata-1 / commits / 260821732

completed configuration and customization of d3pie

Costa Tsaousis (ktsaou) committed Jan 28, 2018 at 06:17 UTC 26082173234fbb42479388f9ec6c04dd494562fb
2 files changed +121 -80
web/dashboard.js
+67 -40
@@ -193,7 +193,7 @@ var NETDATA = window.NETDATA || {};
193 other: '#aaaaaa',
194 mainlabel: '#333333',
195 percentage: '#dddddd',
196 - value: '#cccc44',
196 + value: '#aaaa22',
197 tooltip_bg: '#000000',
198 tooltip_fg: '#efefef',
199 segment_stroke: "#ffffff",
@@ -7219,6 +7219,24 @@ var NETDATA = window.NETDATA || {};
7219 return content;
7220 };
7221
7222 + NETDATA.d3pieDateRange = function(state, data, index) {
7223 + var dt = Math.round((data.before - data.after + 1) / data.points);
7224 + var dt_str = NETDATA.seconds4human(dt);
7225 +
7226 + var before = data.result.data[index].time;
7227 + var after = before - (dt * 1000);
7228 +
7229 + var d1 = NETDATA.dateTime.localeDateString(after);
7230 + var t1 = NETDATA.dateTime.localeTimeString(after);
7231 + var d2 = NETDATA.dateTime.localeDateString(before);
7232 + var t2 = NETDATA.dateTime.localeTimeString(before);
7233 +
7234 + if(d1 === d2)
7235 + return d1 + ' ' + t1 + ' to ' + t2 + ', ' + dt_str;
7236 +
7237 + return d1 + ' ' + t1 + ' to ' + d2 + ' ' + t2 + ', ' + dt_str;
7238 + };
7239 +
7240 NETDATA.d3pieSetSelection = function(state, t) {
7241 if(state.timeIsVisible(t) !== true)
7242 return NETDATA.d3pieClearSelection(state, true);
@@ -7232,7 +7250,7 @@ var NETDATA = window.NETDATA || {};
7250 if(state.tmp.d3pie_timer === undefined) {
7251 state.tmp.d3pie_timer = NETDATA.timeout.set(function() {
7252 state.tmp.d3pie_timer = undefined;
7235 - NETDATA.d3pieChange(state, NETDATA.d3pieSetContent(state, state.data, slot));
7253 + NETDATA.d3pieChange(state, NETDATA.d3pieSetContent(state, state.data, slot), NETDATA.d3pieDateRange(state, state.data, slot));
7254 }, 0);
7255 }
7256
@@ -7246,30 +7264,32 @@ var NETDATA = window.NETDATA || {};
7264 }
7265
7266 if(state.isAutoRefreshable() === true && state.data !== null && force !== true) {
7249 - NETDATA.d3pieChange(state, NETDATA.d3pieSetContent(state, state.data, 0));
7267 + NETDATA.d3pieChartUpdate(state, state.data);
7268 }
7269 else {
7252 - NETDATA.d3pieChange(state, [ { label: 'no data', value: 1, color: '#666666' } ]);
7270 + NETDATA.d3pieChange(state, [ { label: 'no data', value: 1, color: '#666666' } ], 'no data available');
7271 }
7272
7273 return true;
7274 };
7275
7258 - NETDATA.d3pieChange = function(state, content) {
7259 - var subtitle = NETDATA.dataAttribute(state.element, 'd3pie-subtitle', state.units_current);
7260 - if(subtitle !== state.d3pie_subtitle) {
7261 - state.d3pie_subtitle = subtitle;
7262 - state.d3pie_instance.updateProp("header.subtitle.text", state.d3pie_subtitle);
7263 - }
7276 + NETDATA.d3pieChange = function(state, content, footer) {
7277 + state.d3pie_options.data.content = content;
7278 +
7279 + if(state.d3pie_forced_subtitle === null)
7280 + state.d3pie_options.header.subtitle.text = state.units_current;
7281
7265 - state.d3pie_instance.updateProp("data.content", content);
7282 + if(state.d3pie_forced_footer === null)
7283 + state.d3pie_options.footer.text = footer;
7284 +
7285 + state.d3pie_instance.destroy();
7286 + state.d3pie_instance = new d3pie(state.element_chart, state.d3pie_options);
7287
7288 return true;
7289 };
7290
7291 NETDATA.d3pieChartUpdate = function(state, data) {
7271 - var content = NETDATA.d3pieSetContent(state, data, 0);
7272 - return NETDATA.d3pieChange(state, content);
7292 + return NETDATA.d3pieChange(state, NETDATA.d3pieSetContent(state, data, 0), NETDATA.d3pieDateRange(state, data, 0));
7293 };
7294
7295 NETDATA.d3pieChartCreate = function(state, data) {
@@ -7278,35 +7298,41 @@ var NETDATA = window.NETDATA || {};
7298 // console.log('id = ' + state.element_chart.id);
7299
7300 var content = NETDATA.d3pieSetContent(state, data, 0);
7281 - state.d3pie_subtitle = NETDATA.dataAttribute(state.element, 'd3pie-subtitle', state.units_current);
7301 +
7302 + state.d3pie_forced_title = NETDATA.dataAttribute(state.element, 'd3pie-title', null);
7303 + state.d3pie_forced_subtitle = NETDATA.dataAttribute(state.element, 'd3pie-subtitle', null);
7304 + state.d3pie_forced_footer = NETDATA.dataAttribute(state.element, 'd3pie-footer', null);
7305
7306 state.d3pie_options = {
7307 header: {
7308 title: {
7286 - text: NETDATA.dataAttribute(state.element, 'd3pie-title', state.title),
7309 + text: (state.d3pie_forced_title !== null) ? state.d3pie_forced_title : state.title,
7310 color: NETDATA.dataAttribute(state.element, 'd3pie-title-color', NETDATA.themes.current.d3pie.title),
7288 - fontSize: NETDATA.dataAttribute(state.element, 'd3pie-title-fontsize', 14),
7311 + fontSize: NETDATA.dataAttribute(state.element, 'd3pie-title-fontsize', 12),
7312 + fontWeight: NETDATA.dataAttribute(state.element, 'd3pie-title-fontweight', "bold"),
7313 font: NETDATA.dataAttribute(state.element, 'd3pie-title-font', "arial")
7314 },
7315 subtitle: {
7292 - text: state.d3pie_subtitle,
7316 + text: (state.d3pie_forced_subtitle !== null) ? state.d3pie_forced_subtitle : state.units_current,
7317 color: NETDATA.dataAttribute(state.element, 'd3pie-subtitle-color', NETDATA.themes.current.d3pie.subtitle),
7294 - fontSize: NETDATA.dataAttribute(state.element, 'd3pie-subtitle-fontsize', 11),
7318 + fontSize: NETDATA.dataAttribute(state.element, 'd3pie-subtitle-fontsize', 10),
7319 + fontWeight: NETDATA.dataAttribute(state.element, 'd3pie-subtitle-fontweight', "normal"),
7320 font: NETDATA.dataAttribute(state.element, 'd3pie-subtitle-font', "arial")
7321 },
7322 titleSubtitlePadding: 1
7323 },
7324 footer: {
7300 - text: NETDATA.dataAttribute(state.element, 'd3pie-footer', ''),
7325 + text: (state.d3pie_forced_footer !== null) ? state.d3pie_forced_footer : NETDATA.d3pieDateRange(state, data, 0),
7326 color: NETDATA.dataAttribute(state.element, 'd3pie-footer-color', NETDATA.themes.current.d3pie.footer),
7302 - fontSize: NETDATA.dataAttribute(state.element, 'd3pie-footer-fontsize', 10),
7327 + fontSize: NETDATA.dataAttribute(state.element, 'd3pie-footer-fontsize', 9),
7328 + fontWeight: NETDATA.dataAttribute(state.element, 'd3pie-footer-fontweight', "bold"),
7329 font: NETDATA.dataAttribute(state.element, 'd3pie-footer-font', "arial"),
7304 - location: NETDATA.dataAttribute(state.element, 'd3pie-footer-location', "bottom-left")
7330 + location: NETDATA.dataAttribute(state.element, 'd3pie-footer-location', "bottom-center") // bottom-left, bottom-center, bottom-right
7331 },
7332 size: {
7333 canvasHeight: state.chartHeight(),
7334 canvasWidth: state.chartWidth(),
7309 - pieInnerRadius: NETDATA.dataAttribute(state.element, 'd3pie-pieinnerradius', "0%"),
7335 + pieInnerRadius: NETDATA.dataAttribute(state.element, 'd3pie-pieinnerradius', "45%"),
7336 pieOuterRadius: NETDATA.dataAttribute(state.element, 'd3pie-pieouterradius', "80%")
7337 },
7338 data: {
@@ -7330,7 +7356,7 @@ var NETDATA = window.NETDATA || {};
7356 // label, value, percentage, label-value1, label-value2, label-percentage1, label-percentage2
7357 format: NETDATA.dataAttribute(state.element, 'd3pie-labels-outer-format', "label-value1"),
7358 hideWhenLessThanPercentage: NETDATA.dataAttribute(state.element, 'd3pie-labels-outer-hidewhenlessthanpercentage', null),
7333 - pieDistance: 20
7359 + pieDistance: NETDATA.dataAttribute(state.element, 'd3pie-labels-outer-piedistance', 15)
7360 },
7361 inner: {
7362 // label, value, percentage, label-value1, label-value2, label-percentage1, label-percentage2
@@ -7338,26 +7364,33 @@ var NETDATA = window.NETDATA || {};
7364 hideWhenLessThanPercentage: NETDATA.dataAttribute(state.element, 'd3pie-labels-inner-hidewhenlessthanpercentage', 2)
7365 },
7366 mainLabel: {
7341 - color: NETDATA.dataAttribute(state.element, 'd3pie-labels-mainLabel-color', NETDATA.themes.current.d3pie.mainlabel),
7367 + color: NETDATA.dataAttribute(state.element, 'd3pie-labels-mainLabel-color', NETDATA.themes.current.d3pie.mainlabel), // or 'segment' for dynamic color
7368 font: NETDATA.dataAttribute(state.element, 'd3pie-labels-mainLabel-font', "arial"),
7343 - fontSize: NETDATA.dataAttribute(state.element, 'd3pie-labels-mainLabel-fontsize', 10)
7369 + fontSize: NETDATA.dataAttribute(state.element, 'd3pie-labels-mainLabel-fontsize', 10),
7370 + fontWeight: NETDATA.dataAttribute(state.element, 'd3pie-labels-mainLabel-fontweight', "normal")
7371 },
7372 percentage: {
7373 color: NETDATA.dataAttribute(state.element, 'd3pie-labels-percentage-color', NETDATA.themes.current.d3pie.percentage),
7374 font: NETDATA.dataAttribute(state.element, 'd3pie-labels-percentage-font', "arial"),
7375 fontSize: NETDATA.dataAttribute(state.element, 'd3pie-labels-percentage-fontsize', 10),
7376 + fontWeight: NETDATA.dataAttribute(state.element, 'd3pie-labels-percentage-fontweight', "bold"),
7377 decimalPlaces: 0
7378 },
7379 value: {
7380 color: NETDATA.dataAttribute(state.element, 'd3pie-labels-value-color', NETDATA.themes.current.d3pie.value),
7381 font: NETDATA.dataAttribute(state.element, 'd3pie-labels-value-font', "arial"),
7354 - fontSize: NETDATA.dataAttribute(state.element, 'd3pie-labels-value-fontsize', 10)
7382 + fontSize: NETDATA.dataAttribute(state.element, 'd3pie-labels-value-fontsize', 10),
7383 + fontWeight: NETDATA.dataAttribute(state.element, 'd3pie-labels-value-fontweight', "bold")
7384 },
7385 lines: {
7386 enabled: NETDATA.dataAttributeBoolean(state.element, 'd3pie-labels-lines-enabled', true),
7387 style: NETDATA.dataAttribute(state.element, 'd3pie-labels-lines-style', "curved"),
7388 color: NETDATA.dataAttribute(state.element, 'd3pie-labels-lines-color', "segment") // "segment" or a hex color
7389 },
7390 + truncation: {
7391 + enabled: NETDATA.dataAttributeBoolean(state.element, 'd3pie-labels-truncation-enabled', false),
7392 + truncateLength: NETDATA.dataAttribute(state.element, 'd3pie-labels-truncation-truncatelength', 30)
7393 + },
7394 formatter: function(context) {
7395 // console.log(context);
7396 if(context.part === 'value')
@@ -7371,21 +7404,21 @@ var NETDATA = window.NETDATA || {};
7404 effects: {
7405 load: {
7406 effect: "none", // none / default
7374 - speed: 0
7407 + speed: 0 // commented in the d3pie code to speed it up
7408 },
7409 pullOutSegmentOnClick: {
7377 - effect: "none", // none / linear / bounce / elastic / back
7378 - speed: 300,
7379 - size: 10
7410 + effect: "bounce", // none / linear / bounce / elastic / back
7411 + speed: 400,
7412 + size: 5
7413 },
7381 - highlightSegmentOnMouseover: false,
7382 - highlightLuminosity: 1
7414 + highlightSegmentOnMouseover: true,
7415 + highlightLuminosity: -0.2
7416 },
7417 tooltips: {
7418 enabled: false,
7419 type: "placeholder", // caption|placeholder
7420 string: "",
7388 - placeholderParser: null,
7421 + placeholderParser: null, // function
7422 styles: {
7423 fadeInSpeed: 250,
7424 backgroundColor: NETDATA.themes.current.d3pie.tooltip_bg,
@@ -7393,7 +7426,7 @@ var NETDATA = window.NETDATA || {};
7426 color: NETDATA.themes.current.d3pie.tooltip_fg,
7427 borderRadius: 2,
7428 font: "arial",
7396 - fontSize: 10,
7429 + fontSize: 12,
7430 padding: 4
7431 }
7432 },
@@ -7429,12 +7462,6 @@ var NETDATA = window.NETDATA || {};
7462 };
7463
7464 state.d3pie_instance = new d3pie(state.element_chart, state.d3pie_options);
7432 -
7433 - // FIXME: temporary fix because the first update does not render the pie
7434 - NETDATA.d3pieChartUpdate(state, data);
7435 -
7436 - // console.log(state.d3pie_instance);
7437 -
7465 return true;
7466 };
7467
web/lib/d3pie-0.2.1-netdata-1.js
+54 -40
@@ -42,12 +42,14 @@ var defaultSettings = {
42 text: "",
43 color: "#333333",
44 fontSize: 18,
45 + fontWeight: "bold",
46 font: "arial"
47 },
48 subtitle: {
49 text: "",
50 color: "#666666",
51 fontSize: 14,
52 + fontWeight: "bold",
53 font: "arial"
54 },
55 location: "top-center",
@@ -57,6 +59,7 @@ var defaultSettings = {
59 text: "",
60 color: "#666666",
61 fontSize: 14,
62 + fontWeight: "bold",
63 font: "arial",
64 location: "left"
65 },
@@ -95,16 +98,19 @@ var defaultSettings = {
98 mainLabel: {
99 color: "#333333",
100 font: "arial",
101 + fontWeight: "normal",
102 fontSize: 10
103 },
104 percentage: {
105 color: "#dddddd",
106 font: "arial",
107 + fontWeight: "bold",
108 fontSize: 10,
109 decimalPlaces: 0
110 },
111 value: {
112 color: "#cccc44",
113 + fontWeight: "bold",
114 font: "arial",
115 fontSize: 10
116 },
@@ -121,31 +127,32 @@ var defaultSettings = {
127 },
128 effects: {
129 load: {
124 - effect: "default",
130 + effect: "none", // "default", commented in the code
131 speed: 1000
132 },
133 pullOutSegmentOnClick: {
128 - effect: "bounce",
134 + effect: "none", // "bounce", commented in the code
135 speed: 300,
136 size: 10
137 },
132 - highlightSegmentOnMouseover: true,
138 + highlightSegmentOnMouseover: false,
139 highlightLuminosity: -0.2
140 },
141 tooltips: {
142 enabled: false,
143 type: "placeholder", // caption|placeholder
138 - string: "",
139 - placeholderParser: null,
144 + string: "",
145 + placeholderParser: null,
146 styles: {
141 - fadeInSpeed: 250,
142 - backgroundColor: "#000000",
143 - backgroundOpacity: 0.5,
144 - color: "#efefef",
145 - borderRadius: 2,
146 - font: "arial",
147 - fontSize: 10,
148 - padding: 4
147 + fadeInSpeed: 250,
148 + backgroundColor: "#000000",
149 + backgroundOpacity: 0.5,
150 + color: "#efefef",
151 + borderRadius: 2,
152 + font: "arial",
153 + fontWeight: "bold",
154 + fontSize: 10,
155 + padding: 4
156 }
157 },
158 misc: {
@@ -265,9 +272,8 @@ var helpers = {
272 return svg;
273 },
274
275 +/*
276 whenIdExists: function(id, callback) {
269 - callback();
270 - /*
277 var inc = 1;
278 var giveupIterationCount = 1000;
279
@@ -281,12 +287,9 @@ var helpers = {
287 }
288 inc++;
289 }, 1);
284 - */
290 },
291
292 whenElementsExist: function(els, callback) {
288 - callback();
289 - /*
293 var inc = 1;
294 var giveupIterationCount = 1000;
295
@@ -307,8 +310,8 @@ var helpers = {
310 }
311 inc++;
312 }, 1);
310 - */
313 },
314 +*/
315
316 shuffleArray: function(array) {
317 var currentIndex = array.length, tmpVal, randomIndex;
@@ -838,7 +841,10 @@ var labels = {
841 })
842 .style("font-size", settings.mainLabel.fontSize + "px")
843 .style("font-family", settings.mainLabel.font)
841 - .style("fill", settings.mainLabel.color);
844 + .style("font-weight", settings.mainLabel.fontWeight)
845 + .style("fill", function(d, i) {
846 + return (settings.mainLabel.color === "segment") ? pie.options.colors[i] : settings.mainLabel.color;
847 + });
848 }
849
850 // 2. Add the percentage label
@@ -861,6 +867,7 @@ var labels = {
867 })
868 .style("font-size", settings.percentage.fontSize + "px")
869 .style("font-family", settings.percentage.font)
870 + .style("font-weight", settings.percentage.fontWeight)
871 .style("fill", settings.percentage.color);
872 }
873
@@ -878,6 +885,7 @@ var labels = {
885 })
886 .style("font-size", settings.value.fontSize + "px")
887 .style("font-family", settings.value.font)
888 + .style("font-weight", settings.value.fontWeight)
889 .style("fill", settings.value.color);
890 }
891 },
@@ -1066,7 +1074,7 @@ var labels = {
1074 fadeInLabelsAndLines: function(pie) {
1075
1076 // fade in the labels when the load effect is complete - or immediately if there's no load effect
1069 - var loadSpeed = (pie.options.effects.load.effect === "default") ? pie.options.effects.load.speed : 1;
1077 + //var loadSpeed = (pie.options.effects.load.effect === "default") ? pie.options.effects.load.speed : 1;
1078 //setTimeout(function() {
1079 var labelFadeInTime = (pie.options.effects.load.effect === "default") ? 400 : 1; // 400 is hardcoded for the present
1080
@@ -1332,10 +1340,10 @@ var segments = {
1340 .attr("class", pie.cssPrefix + "arc");
1341
1342 // if we're not fading in the pie, just set the load speed to 0
1335 - var loadSpeed = loadEffects.speed;
1336 - if (loadEffects.effect === "none") {
1337 - loadSpeed = 0;
1338 - }
1343 + //var loadSpeed = loadEffects.speed;
1344 + //if (loadEffects.effect === "none") {
1345 + // loadSpeed = 0;
1346 + //}
1347
1348 g.append("path")
1349 .attr("id", function(d, i) { return pie.cssPrefix + "segment" + i; })
@@ -1352,12 +1360,14 @@ var segments = {
1360 //.ease(d3.easeCubicInOut)
1361 //.duration(loadSpeed)
1362 .attr("data-index", function(d, i) { return i; })
1355 - .attr("d", pie.arc);
1356 -/* function(b) {
1357 - //var i = d3.interpolate({ value: 0 }, b);
1363 + .attr("d", arc);
1364 +/*
1365 + .attrTween("d", function(b) {
1366 + var i = d3.interpolate({ value: 0 }, b);
1367 return function(t) {
1359 - console.log(b);
1360 - return pie.arc(i(t));
1368 + var ret = pie.arc(i(t));
1369 + console.log(ret);
1370 + return ret;
1371 };
1372 });
1373 */
@@ -1498,9 +1508,9 @@ var segments = {
1508 segments.maybeCloseOpenSegment();
1509
1510 d3.select(segment)
1501 - //.transition()
1502 - //.ease(segments.effectMap[pie.options.effects.pullOutSegmentOnClick.effect])
1503 - //.duration(pie.options.effects.pullOutSegmentOnClick.speed)
1511 + .transition()
1512 + .ease(segments.effectMap[pie.options.effects.pullOutSegmentOnClick.effect])
1513 + .duration(pie.options.effects.pullOutSegmentOnClick.speed)
1514 .attr("transform", function(d, i) {
1515 var c = pie.arc.centroid(d),
1516 x = c[0],
@@ -1518,15 +1528,15 @@ var segments = {
1528 },
1529
1530 maybeCloseOpenSegment: function() {
1521 - if (d3.selectAll("." + pie.cssPrefix + "expanded").size() > 0) {
1531 + if (typeof pie !== 'undefined' && d3.selectAll("." + pie.cssPrefix + "expanded").size() > 0) {
1532 segments.closeSegment(pie, d3.select("." + pie.cssPrefix + "expanded").node());
1533 }
1534 },
1535
1536 closeSegment: function(pie, segment) {
1537 d3.select(segment)
1528 - //.transition()
1529 - //.duration(400)
1538 + .transition()
1539 + .duration(400)
1540 .attr("transform", "translate(0,0)")
1541 .on("end", function(d, i) {
1542 d3.select(segment).attr("class", "");
@@ -1612,6 +1622,7 @@ var text = {
1622 })
1623 .attr("fill", function(d) { return d.color; })
1624 .style("font-size", function(d) { return d.fontSize + "px"; })
1625 + .style("font-weight", function(d) { return d.fontWeight; })
1626 .style("font-family", function(d) { return d.font; });
1627 },
1628
@@ -1674,6 +1685,7 @@ var text = {
1685 })
1686 .attr("fill", function(d) { return d.color; })
1687 .style("font-size", function(d) { return d.fontSize + "px"; })
1688 + .style("font-weight", function(d) { return d.fontWeight; })
1689 .style("font-family", function(d) { return d.font; });
1690 },
1691
@@ -1718,6 +1730,7 @@ var text = {
1730 })
1731 .attr("fill", function(d) { return d.color; })
1732 .style("font-size", function(d) { return d.fontSize + "px"; })
1733 + .style("font-weight", function(d) { return d.fontWeight; })
1734 .style("font-family", function(d) { return d.font; });
1735 },
1736
@@ -1792,6 +1805,7 @@ var tt = {
1805 .append("text")
1806 .attr("fill", function(d) { return pie.options.tooltips.styles.color; })
1807 .style("font-size", function(d) { return pie.options.tooltips.styles.fontSize; })
1808 + .style("font-weight", function(d) { return pie.options.tooltips.styles.fontWeight; })
1809 .style("font-family", function(d) { return pie.options.tooltips.styles.font; })
1810 .text(function(d, i) {
1811 var caption = pie.options.tooltips.string;
@@ -2088,12 +2102,12 @@ var tt = {
2102
2103 // the footer never moves. Put it in place now
2104 var self = this;
2091 - helpers.whenIdExists(this.cssPrefix + "footer", function() {
2105 + //helpers.whenIdExists(this.cssPrefix + "footer", function() {
2106 text.positionFooter(self);
2107 var d3 = helpers.getDimensions(self.cssPrefix + "footer");
2108 self.textComponents.footer.h = d3.h;
2109 self.textComponents.footer.w = d3.w;
2096 - });
2110 + //});
2111
2112 // now create the pie chart and position everything accordingly
2113 var reqEls = [];
@@ -2101,7 +2115,7 @@ var tt = {
2115 if (this.textComponents.subtitle.exists) { reqEls.push(this.cssPrefix + "subtitle"); }
2116 if (this.textComponents.footer.exists) { reqEls.push(this.cssPrefix + "footer"); }
2117
2104 - helpers.whenElementsExist(reqEls, function() {
2118 + //helpers.whenElementsExist(reqEls, function() {
2119 if (self.textComponents.title.exists) {
2120 var d1 = helpers.getDimensions(self.cssPrefix + "title");
2121 self.textComponents.title.h = d1.h;
@@ -2171,7 +2185,7 @@ var tt = {
2185 }
2186
2187 segments.addSegmentEventHandlers(self);
2174 - });
2188 + //});
2189 };
2190
2191 var _getPercentage = function(value, total, decimalPlaces) {