Hướng dẫn thử thách
1 / 1
Build a Bar Chart
In this project you will use the data from `https://cdn.freecodecamp.org/curriculum/labs/data/bar-chart/GDP-data.json` to build a bar chart.
The tests require axes to be generated using the D3 axis property, which automatically generates ticks along the axis. These ticks are required for passing the D3 tests because their positions are used to determine alignment of graphed elements.
**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
**User Stories:**
1. Your chart should have a title with a corresponding `id="title"`.
1. Your chart should have a `g` element x-axis with a corresponding `id="x-axis"`.
1. Your chart should have a `g` element y-axis with a corresponding `id="y-axis"`.
1. Both axes should contain multiple tick labels, each with a corresponding `class="tick"`.
1. Your chart should have a `rect` element for each data point with a corresponding `class="bar"` displaying the data.
1. Each `.bar` should have the attributes `data-date` and `data-gdp` containing `date` and `GDP` values.
1. The `.bar` elements' `data-date` attributes should match the order of the provided data.
1. The `.bar` elements' `data-gdp` attributes should match the order of the provided data.
1. Each `.bar` element's height should accurately represent the data's corresponding `GDP`.
1. The `data-date` attribute and its corresponding `.bar` element should align with the corresponding value on the x-axis.
1. The `data-gdp` attribute and its corresponding `.bar` element should align with the corresponding value on the y-axis.
1. You should be able to mouse over an area and see a tooltip with a corresponding `id="tooltip"` which displays more information about the area.
1. Your tooltip should have a `data-date` attribute that corresponds to the `data-date` of the active area.
# --before-all--
```js
const GDPDataJson = {
"errors": {},
"id": 120140,
"source_name": "Federal Reserve Economic Data",
"source_code": "FRED",
"code": "GDP",
"name": "Gross Domestic Product, 1 Decimal",
"urlize_name": "Gross-Domestic-Product-1-Decimal",
"display_url": "http://research.stlouisfed.org/fred2/data/GDP.txt",
"description": "Units: Billions of Dollars\nSeasonal Adjustment: Seasonally Adjusted Annual Rate\nNotes: A Guide to the National Income and Product Accounts of the United States (NIPA) - (http://www.bea.gov/national/pdf/nipaguid.pdf)",
"updated_at": "2015-12-14T20:00:28.561Z",
"frequency": "quarterly",
"from_date": "1947-01-01",
"to_date": "2015-07-01",
"column_names": [
"DATE",
"VALUE"
],
"private": false,
"type": null,
"premium": false,
"data": [
[
"1947-01-01",
243.1
],
[
"1947-04-01",
246.3
],
[
"1947-07-01",
250.1
],
[
"1947-10-01",
260.3
],
[
"1948-01-01",
266.2
],
[
"1948-04-01",
272.9
],
[
"1948-07-01",
279.5
],
[
"1948-10-01",
280.7
],
[
"1949-01-01",
275.4
],
[
"1949-04-01",
271.7
],
[
"1949-07-01",
273.3
],
[
"1949-10-01",
271
],
[
"1950-01-01",
281.2
],
[
"1950-04-01",
290.7
],
[
"1950-07-01",
308.5
],
[
"1950-10-01",
320.3
],
[
"1951-01-01",
336.4
],
[
"1951-04-01",
344.5
],
[
"1951-07-01",
351.8
],
[
"1951-10-01",
356.6
],
[
"1952-01-01",
360.2
],
[
"1952-04-01",
361.4
],
[
"1952-07-01",
368.1
],
[
"1952-10-01",
381.2
],
[
"1953-01-01",
388.5
],
[
"1953-04-01",
392.3
],
[
"1953-07-01",
391.7
],
[
"1953-10-01",
386.5
],
[
"1954-01-01",
385.9
],
[
"1954-04-01",
386.7
],
[
"1954-07-01",
391.6
],
[
"1954-10-01",
400.3
],
[
"1955-01-01",
413.8
],
[
"1955-04-01",
422.2
],
[
"1955-07-01",
430.9
],
[
"1955-10-01",
437.8
],
[
"1956-01-01",
440.5
],
[
"1956-04-01",
446.8
],
[
"1956-07-01",
452
],
[
"1956-10-01",
461.3
],
[
"1957-01-01",
470.6
],
[
"1957-04-01",
472.8
],
[
"1957-07-01",
480.3
],
[
"1957-10-01",
475.7
],
[
"1958-01-01",
468.4
],
[
"1958-04-01",
472.8
],
[
"1958-07-01",
486.7
],
[
"1958-10-01",
500.4
],
[
"1959-01-01",
511.1
],
[
"1959-04-01",
524.2
],
[
"1959-07-01",
525.2
],
[
"1959-10-01",
529.3
],
[
"1960-01-01",
543.3
],
[
"1960-04-01",
542.7
],
[
"1960-07-01",
546
],
[
"1960-10-01",
541.1
],
[
"1961-01-01",
545.9
],
[
"1961-04-01",
557.4
],
[
"1961-07-01",
568.2
],
[
"1961-10-01",
581.6
],
[
"1962-01-01",
595.2
],
[
"1962-04-01",
602.6
],
[
"1962-07-01",
609.6
],
[
"1962-10-01",
613.1
],
[
"1963-01-01",
622.7
],
[
"1963-04-01",
631.8
],
[
"1963-07-01",
645
],
[
"1963-10-01",
654.8
],
[
"1964-01-01",
671.1
],
[
"1964-04-01",
680.8
],
[
"1964-07-01",
692.8
],
[
"1964-10-01",
698.4
],
[
"1965-01-01",
719.2
],
[
"1965-04-01",
732.4
],
[
"1965-07-01",
750.2
],
[
"1965-10-01",
773.1
],
[
"1966-01-01",
797.3
],
[
"1966-04-01",
807.2
],
[
"1966-07-01",
820.8
],
[
"1966-10-01",
834.9
],
[
"1967-01-01",
846
],
[
"1967-04-01",
851.1
],
[
"1967-07-01",
866.6
],
[
"1967-10-01",
883.2
],
[
"1968-01-01",
911.1
],
[
"1968-04-01",
936.3
],
[
"1968-07-01",
952.3
],
[
"1968-10-01",
970.1
],
[
"1969-01-01",
995.4
],
[
"1969-04-01",
1011.4
],
[
"1969-07-01",
1032
],
[
"1969-10-01",
1040.7
],
[
"1970-01-01",
1053.5
],
[
"1970-04-01",
1070.1
],
[
"1970-07-01",
1088.5
],
[
"1970-10-01",
1091.5
],
[
"1971-01-01",
1137.8
],
[
"1971-04-01",
1159.4
],
[
"1971-07-01",
1180.3
],
[
"1971-10-01",
1193.6
],
[
"1972-01-01",
1233.8
],
[
"1972-04-01",
1270.1
],
[
"1972-07-01",
1293.8
],
[
"1972-10-01",
1332
],
[
"1973-01-01",
1380.7
],
[
"1973-04-01",
1417.6
],
[
"1973-07-01",
1436.8
],
[
"1973-10-01",
1479.1
],
[
"1974-01-01",
1494.7
],
[
"1974-04-01",
1534.2
],
[
"1974-07-01",
1563.4
],
[
"1974-10-01",
1603
],
[
"1975-01-01",
1619.6
],
[
"1975-04-01",
1656.4
],
[
"1975-07-01",
1713.8
],
[
"1975-10-01",
1765.9
],
[
"1976-01-01",
1824.5
],
[
"1976-04-01",
1856.9
],
[
"1976-07-01",
1890.5
],
[
"1976-10-01",
1938.4
],
[
"1977-01-01",
1992.5
],
[
"1977-04-01",
2060.2
],
[
"1977-07-01",
2122.4
],
[
"1977-10-01",
2168.7
],
[
"1978-01-01",
2208.7
],
[
"1978-04-01",
2336.6
],
[
"1978-07-01",
2398.9
],
[
"1978-10-01",
2482.2
],
[
"1979-01-01",
2531.6
],
[
"1979-04-01",
2595.9
],
[
"1979-07-01",
2670.4
],
[
"1979-10-01",
2730.7
],
[
"1980-01-01",
2796.5
],
[
"1980-04-01",
2799.9
],
[
"1980-07-01",
2860
],
[
"1980-10-01",
2993.5
],
[
"1981-01-01",
3131.8
],
[
"1981-04-01",
3167.3
],
[
"1981-07-01",
3261.2
],
[
"1981-10-01",
3283.5
],
[
"1982-01-01",
3273.8
],
[
"1982-04-01",
3331.3
],
[
"1982-07-01",
3367.1
],
[
"1982-10-01",
3407.8
],
[
"1983-01-01",
3480.3
],
[
"1983-04-01",
3583.8
],
[
"1983-07-01",
3692.3
],
[
"1983-10-01",
3796.1
],
[
"1984-01-01",
3912.8
],
[
"1984-04-01",
4015
],
[
"1984-07-01",
4087.4
],
[
"1984-10-01",
4147.6
],
[
"1985-01-01",
4237
],
[
"1985-04-01",
4302.3
],
[
"1985-07-01",
4394.6
],
[
"1985-10-01",
4453.1
],
[
"1986-01-01",
4516.3
],
[
"1986-04-01",
4555.2
],
[
"1986-07-01",
4619.6
],
[
"1986-10-01",
4669.4
],
[
"1987-01-01",
4736.2
],
[
"1987-04-01",
4821.5
],
[
"1987-07-01",
4900.5
],
[
"1987-10-01",
5022.7
],
[
"1988-01-01",
5090.6
],
[
"1988-04-01",
5207.7
],
[
"1988-07-01",
5299.5
],
[
"1988-10-01",
5412.7
],
[
"1989-01-01",
5527.4
],
[
"1989-04-01",
5628.4
],
[
"1989-07-01",
5711.6
],
[
"1989-10-01",
5763.4
],
[
"1990-01-01",
5890.8
],
[
"1990-04-01",
5974.7
],
[
"1990-07-01",
6029.5
],
[
"1990-10-01",
6023.3
],
[
"1991-01-01",
6054.9
],
[
"1991-04-01",
6143.6
],
[
"1991-07-01",
6218.4
],
[
"1991-10-01",
6279.3
],
[
"1992-01-01",
6380.8
],
[
"1992-04-01",
6492.3
],
[
"1992-07-01",
6586.5
],
[
"1992-10-01",
6697.6
],
[
"1993-01-01",
6748.2
],
[
"1993-04-01",
6829.6
],
[
"1993-07-01",
6904.2
],
[
"1993-10-01",
7032.8
],
[
"1994-01-01",
7136.3
],
[
"1994-04-01",
7269.8
],
[
"1994-07-01",
7352.3
],
[
"1994-10-01",
7476.7
],
[
"1995-01-01",
7545.3
],
[
"1995-04-01",
7604.9
],
[
"1995-07-01",
7706.5
],
[
"1995-10-01",
7799.5
],
[
"1996-01-01",
7893.1
],
[
"1996-04-01",
8061.5
],
[
"1996-07-01",
8159
],
[
"1996-10-01",
8287.1
],
[
"1997-01-01",
8402.1
],
[
"1997-04-01",
8551.9
],
[
"1997-07-01",
8691.8
],
[
"1997-10-01",
8788.3
],
[
"1998-01-01",
8889.7
],
[
"1998-04-01",
8994.7
],
[
"1998-07-01",
9146.5
],
[
"1998-10-01",
9325.7
],
[
"1999-01-01",
9447.1
],
[
"1999-04-01",
9557
],
[
"1999-07-01",
9712.3
],
[
"1999-10-01",
9926.1
],
[
"2000-01-01",
10031
],
[
"2000-04-01",
10278.3
],
[
"2000-07-01",
10357.4
],
[
"2000-10-01",
10472.3
],
[
"2001-01-01",
10508.1
],
[
"2001-04-01",
10638.4
],
[
"2001-07-01",
10639.5
],
[
"2001-10-01",
10701.3
],
[
"2002-01-01",
10834.4
],
[
"2002-04-01",
10934.8
],
[
"2002-07-01",
11037.1
],
[
"2002-10-01",
11103.8
],
[
"2003-01-01",
11230.1
],
[
"2003-04-01",
11370.7
],
[
"2003-07-01",
11625.1
],
[
"2003-10-01",
11816.8
],
[
"2004-01-01",
11988.4
],
[
"2004-04-01",
12181.4
],
[
"2004-07-01",
12367.7
],
[
"2004-10-01",
12562.2
],
[
"2005-01-01",
12813.7
],
[
"2005-04-01",
12974.1
],
[
"2005-07-01",
13205.4
],
[
"2005-10-01",
13381.6
],
[
"2006-01-01",
13648.9
],
[
"2006-04-01",
13799.8
],
[
"2006-07-01",
13908.5
],
[
"2006-10-01",
14066.4
],
[
"2007-01-01",
14233.2
],
[
"2007-04-01",
14422.3
],
[
"2007-07-01",
14569.7
],
[
"2007-10-01",
14685.3
],
[
"2008-01-01",
14668.4
],
[
"2008-04-01",
14813
],
[
"2008-07-01",
14843
],
[
"2008-10-01",
14549.9
],
[
"2009-01-01",
14383.9
],
[
"2009-04-01",
14340.4
],
[
"2009-07-01",
14384.1
],
[
"2009-10-01",
14566.5
],
[
"2010-01-01",
14681.1
],
[
"2010-04-01",
14888.6
],
[
"2010-07-01",
15057.7
],
[
"2010-10-01",
15230.2
],
[
"2011-01-01",
15238.4
],
[
"2011-04-01",
15460.9
],
[
"2011-07-01",
15587.1
],
[
"2011-10-01",
15785.3
],
[
"2012-01-01",
15973.9
],
[
"2012-04-01",
16121.9
],
[
"2012-07-01",
16227.9
],
[
"2012-10-01",
16297.3
],
[
"2013-01-01",
16440.7
],
[
"2013-04-01",
16526.8
],
[
"2013-07-01",
16727.5
],
[
"2013-10-01",
16957.6
],
[
"2014-01-01",
16984.3
],
[
"2014-04-01",
17270
],
[
"2014-07-01",
17522.1
],
[
"2014-10-01",
17615.9
],
[
"2015-01-01",
17649.3
],
[
"2015-04-01",
17913.7
],
[
"2015-07-01",
18064.7
]
]
}
window.fetch = async () =>
({ ok: true, status: 200, json: () => GDPDataJson})
const clock = __FakeTimers.install()
```
# --before-each--
```js
function areShapesAlignedWithTicks(
// NodeList
shapeCollection,
// NodeList
ticksCollection,
// String: 'x', 'y', 'cx', or 'cy'
dimension,
// String: 'data-year', 'data-gdp', 'data-date', 'data-xvalue', 'data-yvalue'
dataAttribute,
// String: 'year', 'minute', 'thousand', 'month'
dataType,
// Shape vertex to compare to axis: String: 'topLeft' or 'center'
positionType
) {
// return early if no shapes
if (shapeCollection.length === 0) {
return false;
}
let aligned = 0;
// get either 'x' or 'y' in case of 'cx' or 'cy'
const coord = dimension.match(/c/g) ? dimension.split('c')[1] : dimension;
let tickValues = [].map.call(ticksCollection, (tick) =>
getTickValue(tick, dataType)
);
const normalValueOrder = tickValues[tickValues.length - 1] > tickValues[0];
// increment may be positive or negative based on axis sort order
const increment = tickValues[1] - tickValues[0];
tickValues = [
tickValues[0] - increment,
...tickValues,
tickValues[tickValues.length - 1] + increment
];
let tickPositions = [].map.call(
ticksCollection,
(tick) => getTickPosition(tick)[coord]
);
const normalPositionOrder = tickPositions[1] > tickPositions[0];
// positionIncrement may be positive or negative based on axis sort order
const positionIncrement = tickPositions[1] - tickPositions[0];
tickPositions = [
tickPositions[0] - positionIncrement,
...tickPositions,
tickPositions[tickPositions.length - 1] + positionIncrement
];
shapeCollection.forEach(function (shape) {
let pos = getShapePosition(shape, dimension, positionType);
let val = getShapeValue(shape, dataAttribute, dataType);
// index is initially off (either -1 or ticksCollection.length), then
// _getSurroundingTicks increments or decrements according to sort order
let surroundingTicks = _getSurroundingTicks(
val,
tickValues,
normalValueOrder
);
if (surroundingTicks.length > 0) {
let prevTick, nextTick;
if (normalPositionOrder) {
[prevTick, nextTick] = surroundingTicks;
} else {
[nextTick, prevTick] = surroundingTicks;
}
let prevPos = tickPositions[prevTick];
let nextPos = tickPositions[nextTick];
// If shape is positioned between the two ticks plus or minus 3px
// A leeway is necessary for this code to work on all chart types.
if (prevPos - 3 <= pos && pos <= nextPos + 3) {
aligned++;
}
}
});
return aligned === shapeCollection.length;
}
function isToolTipHidden(tooltip) {
// Test for width or height of 0
// code is taken from https://github.com/jquery/jquery/blob/main/src/css/hiddenVisibleSelectors.js
// this code is a replacement for jquery .is(':hidden').
// Test for opacity: 0, visibility: hidden, and display: none
// z-index and potentially others are not tested
const { display, opacity, visibility } = window.getComputedStyle(
tooltip,
null
);
return (
!(
tooltip.offsetWidth ||
tooltip.offsetHeight ||
tooltip.getClientRects().length
) ||
opacity === '0' ||
visibility === 'hidden' ||
display === 'none'
);
}
function getRandomIndex(max) {
return Math.floor(Math.random() * max);
}
function getTickValue(item, dataType) {
let val = item.querySelector('text').innerHTML;
switch (dataType) {
case 'minute':
val =
parseInt(val.split(':')[0], 10) + parseInt(val.split(':')[1], 10) / 60;
break;
case 'month':
val = months.indexOf(val.toLowerCase());
break;
case 'thousand':
val = val.split(',').join('');
break;
default:
break;
}
return parseFloat(val);
}
function getTickPosition(tick) {
let x, y;
if (!tick.querySelector('line')) {
throw new Error('Tick does not contain the required line element.');
}
y = tick.querySelector('line').getBoundingClientRect().top;
x = tick.querySelector('line').getBoundingClientRect().left;
return { x: x, y: y };
}
function getShapePosition(item, dimension, positionType) {
let bounds = item.getBoundingClientRect(),
pos = /y/g.test(dimension) ? bounds.top : bounds.left;
if (positionType === 'center') {
pos += (/y/g.test(dimension) ? bounds.height : bounds.width) / 2;
}
return pos;
}
function getShapeValue(item, attribute, dataType) {
let val;
switch (dataType) {
case 'year':
val = new Date(item.getAttribute(attribute)).getFullYear();
break;
case 'minute':
val =
new Date(item.getAttribute(attribute)).getMinutes() +
new Date(item.getAttribute(attribute)).getSeconds() / 60;
break;
case 'month':
val = isNaN(parseInt(item.getAttribute(attribute), 10))
? months.indexOf(item.getAttribute(attribute).toLowerCase())
: item.getAttribute(attribute);
break;
default:
val = item.getAttribute(attribute);
}
return parseFloat(val);
}
function _getSurroundingTicks(value, ticks, normalTickOrder) {
let leftIndex = 0;
let rightIndex = ticks.length;
// Use binary search to find an index before which to insert the value
while (leftIndex !== rightIndex) {
let midIndex = Math.floor((leftIndex + rightIndex) / 2);
if (ticks[midIndex] === value) {
leftIndex = rightIndex = midIndex + 1;
} else if (
(normalTickOrder && ticks[midIndex] > value) ||
(!normalTickOrder && ticks[midIndex] < value)
) {
rightIndex = midIndex;
} else {
leftIndex = midIndex + 1;
}
}
if (leftIndex === 0 || leftIndex === ticks.length) {
return [];
}
return [leftIndex - 1, leftIndex];
}
function triggerMouseEvent(area, mouseEvent) {
let event;
if (document.createEvent) {
// Internet Explorer.
event = document.createEvent('MouseEvent');
// TODO: Provide a link where all the parameters for initMouseEvent are
// documented.
event.initMouseEvent(
mouseEvent,
true,
true,
window,
0,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null
);
} else {
// Non IE browser
event = new MouseEvent(mouseEvent);
}
area.dispatchEvent(event);
}
const timeout = (milliseconds) =>
new Promise((resolve) => setTimeout(resolve, milliseconds));
```
Vượt qua bài kiểm tra hiện tại để mở khóa bài tiếp theo.
main.sh
UTF-8 • Tab Size: 2Kiểm tra bài:⌘↵