date-fns#subDays JavaScript Examples
The following examples show how to use
date-fns#subDays.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: selectDay.js From monsuivipsy with Apache License 2.0 | 5 votes |
SurveyScreen = ({ navigation }) => {
const [diaryData] = useContext(DiaryDataContext);
const startSurvey = (offset) => {
const date = formatDay(beforeToday(offset));
const blackListKeys = ["becks", "NOTES"];
const filtered = Object.keys(diaryData[date] || [])
.filter((key) => !blackListKeys.includes(key))
.reduce((obj, key) => {
obj[key] = diaryData[date][key];
return obj;
}, {});
const dayIsDone = Object.keys(filtered).length !== 0;
const answers = diaryData[date] || {};
const currentSurvey = { date, answers };
return navigation.navigate("day-survey", {
currentSurvey,
editingSurvey: dayIsDone,
});
};
const now = new Date(Date.now());
return (
<SafeAreaView style={styles.safe}>
<BackButton onPress={navigation.goBack} />
<ScrollView style={styles.container}>
<Text style={styles.question}>
Commençons ! Pour quel jour souhaites-tu remplir ton questionnaire ?
</Text>
{[...Array(7)].map((_, i) => {
const value = formatDay(subDays(now, i));
let label = firstLetterUppercase(formatRelativeDate(value));
const blackListKeys = ["becks", "NOTES"];
const filtered = Object.keys(diaryData[value] || [])
.filter((key) => !blackListKeys.includes(key))
.reduce((obj, key) => {
obj[key] = diaryData[value][key];
return obj;
}, {});
const dayIsDone = Object.keys(filtered).length !== 0;
return (
<TouchableOpacity key={i} onPress={() => startSurvey(i)}>
<View style={[styles.answer, dayIsDone ? styles.answerDone : styles.answerNotDone]}>
<View style={styles.answerLabel}>
<CircledIcon color="white" icon={i === 0 ? "TodaySvg" : "YesterdaySvg"} />
<Text style={styles.label}>{label}</Text>
</View>
{dayIsDone ? (
<Done color="#059669" backgroundColor="#D1FAE5" />
) : (
<ArrowUpSvg style={styles.arrowRight} color={colors.BLUE} />
)}
</View>
</TouchableOpacity>
);
})}
<Text style={styles.subtitleTop}>Remarque</Text>
<Text style={styles.subtitle}>
Je ne peux pas remplir au-delà de 7 jours car les informations seront alors moins fidèles
</Text>
</ScrollView>
</SafeAreaView>
);
}
Example #2
Source File: commonFunctions.js From covid19india-react with MIT License | 5 votes |
getIndiaDateYesterday = () => {
return subDays(getIndiaDate(), 1);
}
Example #3
Source File: Calendar.js From umami with MIT License | 5 votes |
DaySelector = ({ date, minDate, maxDate, locale, onSelect }) => {
const dateLocale = getDateLocale(locale);
const weekStartsOn = dateLocale?.options?.weekStartsOn || 0;
const startWeek = startOfWeek(date, {
locale: dateLocale,
weekStartsOn,
});
const startMonth = startOfMonth(date);
const startDay = subDays(startMonth, startMonth.getDay() - weekStartsOn);
const month = date.getMonth();
const year = date.getFullYear();
const daysOfWeek = [];
for (let i = 0; i < 7; i++) {
daysOfWeek.push(addDays(startWeek, i));
}
const days = [];
for (let i = 0; i < 35; i++) {
days.push(addDays(startDay, i));
}
return (
<table>
<thead>
<tr>
{daysOfWeek.map((day, i) => (
<th key={i} className={locale}>
{dateFormat(day, 'EEE', locale)}
</th>
))}
</tr>
</thead>
<tbody>
{chunk(days, 7).map((week, i) => (
<tr key={i}>
{week.map((day, j) => {
const disabled = isBefore(day, minDate) || isAfter(day, maxDate);
return (
<td
key={j}
className={classNames({
[styles.selected]: isSameDay(date, day),
[styles.faded]: day.getMonth() !== month || day.getFullYear() !== year,
[styles.disabled]: disabled,
})}
onClick={!disabled ? () => onSelect(day) : null}
>
{day.getDate()}
</td>
);
})}
</tr>
))}
</tbody>
</table>
);
}
Example #4
Source File: date.js From umami with MIT License | 5 votes |
export function getDateRange(value, locale = 'en-US') {
const now = new Date();
const dateLocale = getDateLocale(locale);
const match = value.match(/^(?<num>[0-9]+)(?<unit>hour|day|week|month|year)$/);
if (!match) return;
const { num, unit } = match.groups;
if (+num === 1) {
switch (unit) {
case 'day':
return {
startDate: startOfDay(now),
endDate: endOfDay(now),
unit: 'hour',
value,
};
case 'week':
return {
startDate: startOfWeek(now, { locale: dateLocale }),
endDate: endOfWeek(now, { locale: dateLocale }),
unit: 'day',
value,
};
case 'month':
return {
startDate: startOfMonth(now),
endDate: endOfMonth(now),
unit: 'day',
value,
};
case 'year':
return {
startDate: startOfYear(now),
endDate: endOfYear(now),
unit: 'month',
value,
};
}
}
switch (unit) {
case 'day':
return {
startDate: subDays(startOfDay(now), num - 1),
endDate: endOfDay(now),
unit,
value,
};
case 'hour':
return {
startDate: subHours(startOfHour(now), num - 1),
endDate: endOfHour(now),
unit,
value,
};
}
}
Example #5
Source File: recapCompletion.js From monsuivipsy with Apache License 2.0 | 4 votes |
RecapCompletion = ({ navigation }) => {
const [diaryData] = React.useContext(DiaryDataContext);
const [startDay, setStartDay] = React.useState(new Date(Date.now()));
const startSurvey = (offset) => {
logEvents.logFeelingStartFromRecap(offset);
const date = formatDay(beforeToday(offset));
const blackListKeys = ["becks", "NOTES"];
const filtered = Object.keys(diaryData[date] || [])
.filter((key) => !blackListKeys.includes(key))
.reduce((obj, key) => {
obj[key] = diaryData[date][key];
return obj;
}, {});
const dayIsDone = Object.keys(filtered).length !== 0;
const answers = diaryData[date] || {};
const currentSurvey = { date, answers };
return navigation.navigate("day-survey", {
currentSurvey,
editingSurvey: dayIsDone,
});
};
useFocusEffect(
React.useCallback(() => {
setStartDay(new Date(Date.now()));
}, [])
);
return (
<View style={styles.safe}>
<Text style={[styles.title, styles.separatorBottom]}>
Complétez les 7 derniers jours pour un meilleur suivi
</Text>
<View style={styles.fil} />
<View style={styles.buttonsContainer}>
{[...Array(7)].map((_, i) => {
const value = formatDay(subDays(startDay, i));
let label = firstLetterUppercase(getFirst3LetterWeekDay(value));
const blackListKeys = ["becks", "NOTES"];
const filtered = Object.keys(diaryData[value] || [])
.filter((key) => !blackListKeys.includes(key))
.reduce((obj, key) => {
obj[key] = diaryData[value][key];
return obj;
}, {});
const dayIsDone = Object.keys(filtered).length !== 0;
const isToday = i === 0;
return (
<TouchableOpacity key={i} onPress={() => startSurvey(i)}>
<View style={styles.answer}>
<View style={styles.answerLabel}>
{dayIsDone ? (
<RoundButtonIcon
backgroundColor="#5DEE5A"
iconColor="#fff"
borderWidth={0.5}
borderColor="#5DEE5A"
icon="validate"
visible={true}
medium
styleContainer={{ marginHorizontal: 0 }}
/>
) : (
<RoundButtonIcon
backgroundColor="#E7F6F8"
iconColor={colors.LIGHT_BLUE}
borderWidth={0.5}
borderColor={colors.LIGHT_BLUE}
icon="small-plus"
visible={true}
medium
styleContainer={{ marginHorizontal: 0 }}
/>
)}
<View style={isToday ? styles.dayLabelTodayContainer : styles.dayLabelContainer}>
<Text style={isToday ? styles.dayLabelToday : styles.dayLabel}>{label}</Text>
</View>
</View>
</View>
</TouchableOpacity>
);
})}
</View>
</View>
);
}
Example #6
Source File: survey-screen.js From monsuivipsy with Apache License 2.0 | 4 votes |
SurveyScreen = ({navigation, route}) => {
const [totalQuestions, setTotalQuestions] = useState(0);
const [questions, setQuestions] = useState([]);
const [question, setQuestion] = useState();
const [answers, setAnswers] = useState();
const [explanation, setExplanation] = useState();
const [currentSurveyItem, setCurrentSurveyItem] = useState();
const [questionId, setQuestionId] = useState();
const [index, setIndex] = useState(route.params?.index);
const [availableData, setAvailableData] = useState();
const updateValues = () => {
if (!availableData || index < 0 || !availableData[index]) return;
setQuestion(availableData[index]?.question);
setAnswers(availableData[index]?.answers);
setExplanation(availableData[index]?.explanation);
setCurrentSurveyItem(index);
setQuestionId(availableData[index]?.id);
};
useEffect(() => {
(async () => {
const q = await buildSurveyData();
if (q) {
setQuestions(q);
setTotalQuestions(q.length);
}
const d = await getAvailableData();
if (d) setAvailableData(d);
})();
}, []);
useEffect(() => {
updateValues();
}, [route, availableData]);
const nextQuestion = (answer) => {
let currentSurvey = {};
if (currentSurveyItem !== 0) {
currentSurvey = {...route.params.currentSurvey};
}
if (answer) {
// if date selection
if (answer.id === 'DATE') {
currentSurvey = {
date: formatDay(beforeToday(answer.dateOffset)),
answers: {},
};
return navigation.navigate('day-survey', {
currentSurvey,
});
} else {
currentSurvey.answers[questionId] = answer;
}
}
let redirection = 'notes';
let nextIndex = -1;
if (!isLastQuestion()) {
const isNextQuestionSkipped = answer.id === 'NEVER';
// getting index of the current question in the 'questions' array
const index = questions.indexOf(currentSurveyItem);
// getting the next index of the next question
const nextQuestionIndex = index + (isNextQuestionSkipped ? 2 : 1);
// if there is a next question, navigate to it
// else go to 'notes'
if (nextQuestionIndex <= questions.length - 1) {
const nextQuestionId = questions[nextQuestionIndex];
redirection = 'question';
nextIndex = nextQuestionId;
}
}
setIndex(nextIndex);
navigation.navigate(redirection, {
currentSurvey,
backRedirect: currentSurveyItem,
index: nextIndex,
});
};
const previousQuestion = () => {
const survey = route.params?.currentSurvey;
// getting index of the current question in the 'questions' array
const index = questions.indexOf(currentSurveyItem);
if (index <= 0) {
navigation.navigate('tabs');
return;
}
// getting the router index of the previous question
let previousQuestionIndex = questions[index - 1];
const previousQuestionId = availableData[previousQuestionIndex].id;
if (!survey?.answers[previousQuestionId])
previousQuestionIndex = questions[index - 2];
setIndex(previousQuestionIndex);
navigation.navigate('question', {
...route.params,
index: previousQuestionIndex,
});
};
const isLastQuestion = () => {
return questions.indexOf(currentSurveyItem) === totalQuestions - 1;
};
if (!question || !availableData) return null;
if (questionId === 'day') {
const now = new Date(Date.now());
return (
<SafeAreaView style={styles.safe}>
<BackButton onPress={previousQuestion} />
<ScrollView style={styles.container}>
<Text style={styles.question}>{question}</Text>
{[...Array(7)].map((_, i) => {
const value = formatDay(subDays(now, i));
let label = firstLetterUppercase(formatRelativeDate(value));
return (
<TouchableOpacity
key={i}
onPress={() => nextQuestion({id: 'DATE', dateOffset: i})}>
<View style={styles.answer}>
<View style={styles.answerLabel}>
<CircledIcon color="white" icon="TodaySvg" />
<Text style={styles.label}>{label}</Text>
</View>
<ArrowUpSvg style={styles.arrowRight} color={colors.BLUE} />
</View>
</TouchableOpacity>
);
})}
<Text style={styles.subtitleTop}>Attention !</Text>
<Text style={styles.subtitle}>
Je ne peux pas remplir au-delà de 7 jours car les informations
seront alors moins fidèles
</Text>
</ScrollView>
{explanation ? <SurveyExplanation explanation={explanation} /> : null}
</SafeAreaView>
);
}
const renderQuestion = () => {
let relativeDate = formatRelativeDate(route.params?.currentSurvey?.date);
if (
!isYesterday(parseISO(route.params?.currentSurvey?.date)) &&
!isToday(parseISO(route.params?.currentSurvey?.date))
)
relativeDate = `le ${relativeDate}`;
return question.replace('{{date}}', relativeDate);
};
return (
<SafeAreaView style={styles.safe}>
<BackButton onPress={previousQuestion} />
<ScrollView style={styles.container}>
<Text style={styles.question}>{renderQuestion()}</Text>
{answers
.filter((answer) => answer.id !== 'NOTES')
.map((answer, index) => (
<TouchableOpacity key={index} onPress={() => nextQuestion(answer)}>
<View style={styles.answer}>
<View style={styles.answerLabel}>
<CircledIcon color={answer.color} icon={answer.icon} />
<Text style={styles.label}>{answer.label}</Text>
</View>
</View>
</TouchableOpacity>
))}
</ScrollView>
{explanation ? <SurveyExplanation explanation={explanation} /> : null}
</SafeAreaView>
);
}
Example #7
Source File: patientdb.js From covid19Nepal-react with MIT License | 4 votes |
function PatientDB(props) {
const [fetched, setFetched] = useState(false);
const [patients, setPatients] = useState([]);
const [filteredPatients, setFilteredPatients] = useState([]);
const [error, setError] = useState('');
const {pathname} = useLocation();
const [colorMode, setColorMode] = useState('genders');
const [scaleMode, setScaleMode] = useState(false);
const [filterDate, setFilterDate] = useState(null);
const [filters, setFilters] = useState({
detectedstate: '',
detecteddistrict: '',
detectedcity: '',
dateannounced: format(subDays(new Date(), 1), 'dd/MM/yyyy'),
});
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
useEffect(() => {
async function fetchRawData() {
const response = await axios.get(
'https://api.nepalcovid19.org/raw_data.json'
);
if (response.data) {
setPatients(response.data.raw_data.reverse());
setFetched(true);
} else {
setError("Couldn't fetch patient data. Try again after sometime.");
console.log(response);
}
}
if (!fetched) {
fetchRawData();
}
}, [fetched]);
const handleFilters = (label, value) => {
setFilters((f) => {
// Create new object (deep copy)
const newFilters = {...f};
newFilters[label] = value;
if (label === 'detectedstate') {
const district = document.getElementById('district');
const city = document.getElementById('city');
// Hide boxes
if (value === '') district.style.display = 'none';
else district.style.display = 'inline';
city.style.display = 'none';
// Default to empty selection
district.selectedIndex = 0;
city.selectedIndex = 0;
newFilters['detecteddistrict'] = '';
newFilters['detectedcity'] = '';
} else if (label === 'detecteddistrict') {
const city = document.getElementById('city');
// Hide box
if (value === '') city.style.display = 'none';
else city.style.display = 'inline';
// Default to empty selection
city.selectedIndex = 0;
newFilters['detectedcity'] = '';
}
return newFilters;
});
};
useEffect(() => {
setFilteredPatients(filterByObject(patients, filters));
}, [patients, filters]);
function getSortedValues(obj, key) {
const setValues = new Set(obj.map((p) => p[key]));
if (setValues.size > 1) setValues.add('');
if (key === 'dateannounced') return Array.from(setValues);
return Array.from(setValues).sort();
}
return (
<div className="PatientsDB">
<Helmet>
<title>Demographics - nepalcovid19.org</title>
<meta name="title" content={`Demographics - nepalcovid19.org`} />
<meta
name="description"
content="A demographical overview of the Nepal population affected by the coronavirus."
/>
</Helmet>
{error ? <div className="alert alert-danger">{error}</div> : ''}
<div className="filters fadeInUp" style={{animationDelay: '0.5s'}}>
<div className="filters-left">
<div className="select">
<select
style={{animationDelay: '0.3s'}}
id="state"
onChange={(event) => {
handleFilters('detectedstate', event.target.value);
}}
>
<option value="" disabled selected>
Select State
</option>
{getSortedValues(patients, 'detectedstate').map(
(state, index) => {
return (
<option key={index} value={state}>
{state === '' ? 'All' : state}
</option>
);
}
)}
</select>
</div>
<div className="select">
<select
style={{animationDelay: '0.4s', display: 'none'}}
id="district"
onChange={(event) => {
handleFilters('detecteddistrict', event.target.value);
}}
>
<option value="" disabled selected>
Select District
</option>
{getSortedValues(
filterByObject(patients, {
detectedstate: filters.detectedstate,
}),
'detecteddistrict'
).map((district, index) => {
return (
<option key={index} value={district}>
{district === '' ? 'All' : district}
</option>
);
})}
</select>
</div>
<div className="select">
<select
style={{animationDelay: '0.4s', display: 'none'}}
id="city"
onChange={(event) => {
handleFilters('detectedcity', event.target.value);
}}
>
<option value="" disabled selected>
Select City
</option>
{getSortedValues(
filterByObject(patients, {
detectedstate: filters.detectedstate,
detecteddistrict: filters.detecteddistrict,
}),
'detectedcity'
).map((city, index) => {
return (
<option key={index} value={city}>
{city === '' ? 'All' : city}
</option>
);
})}
</select>
</div>
<div className="select">
<select
style={{animationDelay: '0.4s', display: 'none'}}
id="city"
onChange={(event) => {
handleFilters('detectedcity', event.target.value);
}}
>
<option value="" disabled selected>
Select City
</option>
{getSortedValues(
filterByObject(patients, {
detectedstate: filters.detectedstate,
detecteddistrict: filters.detecteddistrict,
}),
'detectedcity'
).map((city, index) => {
return (
<option key={index} value={city}>
{city === '' ? 'All' : city}
</option>
);
})}
</select>
</div>
<div className="select">
<DatePicker
value={filterDate}
minDate={new Date('30-Jan-2020')}
maxDate={subDays(new Date(), 1)}
format="dd/MM/y"
calendarIcon={<Icon.Calendar />}
clearIcon={<Icon.XCircle class={'calendar-close'} />}
onChange={(date) => {
setFilterDate(date);
const fomattedDate = !!date ? format(date, 'dd/MM/yyyy') : '';
handleFilters('dateannounced', fomattedDate);
}}
/>
</div>
{/* <div className="select">
<select
style={{animationDelay: '0.4s'}}
onChange={(event) => {
handleFilters('dateannounced', event.target.value);
}}
>
{Array.from(new Set(patients.map((p) => p.dateannounced))).map(
(date, index) => {
return (
<option key={index} value={date}>
{date}
</option>
);
}
)}
</select>
</div>*/}
</div>
<div className="legend">
{colorMode === 'genders' && (
<div className="legend-left">
<div className="circle is-female"></div>
<h5 className="is-female">Female</h5>
<div className="circle is-male"></div>
<h5 className="is-male">Male</h5>
<div className="circle"></div>
<h5 className="">Unknown</h5>
</div>
)}
{colorMode === 'transmission' && (
<div className="legend-left">
<div className="circle is-local"></div>
<h5 className="is-local">Local</h5>
<div className="circle is-imported"></div>
<h5 className="is-imported">Imported</h5>
<div className="circle"></div>
<h5 className="">TBD</h5>
</div>
)}
{colorMode === 'nationality' && (
<div className="legend-left nationality">
<div className="circle is-np"></div>
<h5 className="is-np">Np</h5>
<div className="circle is-in"></div>
<h5 className="is-in">In</h5>
<div className="circle is-uk"></div>
<h5 className="is-uk">Uk</h5>
<div className="circle is-us"></div>
<h5 className="is-us">Us</h5>
<div className="circle is-th"></div>
<h5 className="is-thailand">Th</h5>
<div className="circle is-ph"></div>
<h5 className="is-ph">Ph</h5>
<div className="circle is-it"></div>
<h5 className="is-it">It</h5>
<div className="circle is-ca"></div>
<h5 className="is-ca">Ca</h5>
<div className="circle is-id"></div>
<h5 className="is-id">Id</h5>
<div className="circle is-mm"></div>
<h5 className="is-mm">Mm</h5>
</div>
)}
<div className={`select ${colorMode}`}>
<select
style={{animationDelay: '0.4s'}}
onChange={(event) => {
setColorMode(event.target.value);
}}
>
<option value="" disabled selected>
Color modes
</option>
<option value="genders">Genders</option>
<option value="transmission">Transmission</option>
<option value="nationality">Nationality</option>
{/* <option value="age">Age</option>*/}
</select>
</div>
</div>
</div>
<div className="header fadeInUp" style={{animationDelay: '0.3s'}}>
<div>
<h1>Demographics</h1>
{/* <h3>No. of Patients: {patients.length}</h3>*/}
<div className="deep-dive">
<h5>Expand</h5>
<input
type="checkbox"
checked={scaleMode}
onChange={(event) => {
setScaleMode(!scaleMode);
}}
className="switch"
/>
</div>
</div>
<h6 className="disclaimer">
Some of the data provided might be missing/unknown as the details have
not been shared by the state/central governments.
</h6>
</div>
<div className="reminder fadeInUp" style={{animationDelay: '1s'}}>
<p>
It is important that we do not think of these as just tiny boxes,
numbers, or just another part of statistics - among these are our
neighbors, our teachers, our healthcare workers, our supermarket
vendors, our friends, our co-workers, our children or our
grandparents.
<br />
<br />
Among these are our people.
</p>
</div>
<div className="patientdb-wrapper">
<Patients
patients={filteredPatients}
colorMode={colorMode}
expand={scaleMode}
/>
</div>
<DownloadBlock patients={patients} />
</div>
);
}
Example #8
Source File: timeseries.js From covid19Nepal-react with MIT License | 4 votes |
function TimeSeries({timeseriesProp, chartType, mode, logMode, isTotal}) {
const {t} = useTranslation();
const [lastDaysCount, setLastDaysCount] = useState(
window.innerWidth > 512 ? Infinity : 30
);
const [timeseries, setTimeseries] = useState({});
const [datapoint, setDatapoint] = useState({});
const [index, setIndex] = useState(0);
const [moving, setMoving] = useState(false);
const svgRef1 = useRef();
const svgRef2 = useRef();
const svgRef3 = useRef();
const svgRef4 = useRef();
const svgRef5 = useRef();
const wrapperRef = useRef();
const dimensions = useResizeObserver(wrapperRef);
const transformTimeSeries = useCallback(
(timeseries) => {
if (timeseries.length > 1) {
const slicedTimeseries = sliceTimeseriesFromEnd(
timeseries,
lastDaysCount
);
setIndex(slicedTimeseries.length - 1);
setTimeseries(slicedTimeseries);
}
},
[lastDaysCount]
);
useEffect(() => {
transformTimeSeries(timeseriesProp);
}, [lastDaysCount, timeseriesProp, transformTimeSeries]);
const graphData = useCallback(
(timeseries) => {
if (!dimensions) return;
const width = dimensions.width;
const height = dimensions.height;
// Margins
const margin = {top: 15, right: 35, bottom: 25, left: 25};
const chartRight = width - margin.right;
const chartBottom = height - margin.bottom;
const T = timeseries.length;
const yBufferTop = 1.2;
const yBufferBottom = 1.1;
setDatapoint(timeseries[T - 1]);
setIndex(T - 1);
const svg1 = d3.select(svgRef1.current);
const svg2 = d3.select(svgRef2.current);
const svg3 = d3.select(svgRef3.current);
const svg4 = d3.select(svgRef4.current);
const svg5 = d3.select(svgRef5.current);
const dateMin = subDays(timeseries[0].date, 1);
const dateMax = addDays(timeseries[T - 1].date, 1);
const xScale = d3
.scaleTime()
.clamp(true)
.domain([dateMin, dateMax])
.range([margin.left, chartRight]);
// Number of x-axis ticks
const numTicksX = width < 480 ? 4 : 7;
const xAxis = (g) =>
g.attr('class', 'x-axis').call(d3.axisBottom(xScale).ticks(numTicksX));
const xAxis2 = (g, yScale) => {
g.attr('class', 'x-axis2')
.call(d3.axisBottom(xScale).tickValues([]).tickSize(0))
.select('.domain')
.style('transform', `translateY(${yScale(0)}px)`);
if (yScale(0) !== chartBottom) g.select('.domain').attr('opacity', 0.4);
else g.select('.domain').attr('opacity', 0);
};
const yAxis = (g, yScale) =>
g
.attr('class', 'y-axis')
.call(d3.axisRight(yScale).ticks(4, '0~s').tickPadding(5));
// Arrays of objects
const plotTotal = chartType === 1;
const dataTypesTotal = [
'totalconfirmed',
'totalactive',
'totalrecovered',
'totaldeceased',
'totaltested',
];
const dataTypesDaily = [
'dailyconfirmed',
'dailyactive',
'dailyrecovered',
'dailydeceased',
'dailytested',
];
const colors = ['#ff073a', '#007bff', '#28a745', '#6c757d', '#201aa2'];
const svgArray = [svg1, svg2, svg3, svg4, svg5];
let yScales;
if (plotTotal) {
const uniformScaleMin = d3.min(timeseries, (d) =>
Math.min(d.totalactive, d.totalrecovered, d.totaldeceased)
);
const uniformScaleMax = d3.max(timeseries, (d) => d.totalconfirmed);
const yScaleUniformLinear = d3
.scaleLinear()
.clamp(true)
.domain([uniformScaleMin, Math.max(1, yBufferTop * uniformScaleMax)])
.nice()
.range([chartBottom, margin.top]);
const yScaleUniformLog = d3
.scaleLog()
.clamp(true)
.domain([
Math.max(1, uniformScaleMin),
Math.max(10, yBufferTop * uniformScaleMax),
])
.nice()
.range([chartBottom, margin.top]);
yScales = dataTypesTotal.map((type) => {
const yScaleLinear = d3
.scaleLinear()
.clamp(true)
.domain([
d3.min(timeseries, (d) => d[type]),
Math.max(1, yBufferTop * d3.max(timeseries, (d) => d[type])),
])
.nice()
.range([chartBottom, margin.top]);
const yScaleLog = d3
.scaleLog()
.clamp(true)
.domain([
Math.max(
1,
d3.min(timeseries, (d) => d[type])
),
Math.max(10, yBufferTop * d3.max(timeseries, (d) => d[type])),
])
.nice()
.range([chartBottom, margin.top]);
if (mode && type !== 'totaltested')
return logMode ? yScaleUniformLog : yScaleUniformLinear;
else return logMode ? yScaleLog : yScaleLinear;
});
} else {
const yScaleDailyUniform = d3
.scaleLinear()
.clamp(true)
.domain([
yBufferBottom *
Math.min(
0,
d3.min(timeseries, (d) => d.dailyactive)
),
Math.max(
1,
yBufferTop *
d3.max(timeseries, (d) =>
Math.max(d.dailyconfirmed, d.dailyrecovered, d.dailydeceased)
)
),
])
.nice()
.range([chartBottom, margin.top]);
yScales = dataTypesDaily.map((type) => {
if (mode && type !== 'dailytested') return yScaleDailyUniform;
const yScaleLinear = d3
.scaleLinear()
.clamp(true)
.domain([
yBufferBottom *
Math.min(
0,
d3.min(timeseries, (d) => d[type])
),
Math.max(1, yBufferTop * d3.max(timeseries, (d) => d[type])),
])
.nice()
.range([chartBottom, margin.top]);
return yScaleLinear;
});
}
/* Focus dots */
const focus = svgArray.map((svg, i) => {
return svg
.selectAll('.focus')
.data([timeseries[T - 1]], (d) => d.date)
.join((enter) =>
enter.append('circle').attr('cx', (d) => xScale(d.date))
)
.attr('class', 'focus')
.attr('fill', colors[i])
.attr('stroke', colors[i])
.attr('r', 4);
});
function mousemove() {
const xm = d3.mouse(this)[0];
const date = xScale.invert(xm);
const bisectDate = d3.bisector((d) => d.date).left;
let i = bisectDate(timeseries, date, 1);
if (0 <= i && i < T) {
if (date - timeseries[i - 1].date < timeseries[i].date - date) --i;
setDatapoint(timeseries[i]);
setIndex(i);
setMoving(true);
const d = timeseries[i];
focus.forEach((f, j) => {
const yScale = yScales[j];
const type = plotTotal ? dataTypesTotal[j] : dataTypesDaily[j];
if (!isNaN(d[type]))
f.attr('cx', xScale(d.date))
.attr('cy', yScale(d[type]))
.attr('opacity', 1);
else f.attr('opacity', 0);
});
}
}
function mouseout() {
setDatapoint(timeseries[T - 1]);
setIndex(T - 1);
setMoving(false);
focus.forEach((f, j) => {
const yScale = yScales[j];
const type = plotTotal ? dataTypesTotal[j] : dataTypesDaily[j];
if (!isNaN(timeseries[T - 1][type]))
f.attr('cx', xScale(timeseries[T - 1].date))
.attr('cy', yScale(timeseries[T - 1][type]))
.attr('opacity', 1);
else f.attr('opacity', 0);
});
}
/* Begin drawing charts */
svgArray.forEach((svg, i) => {
// Transition interval
const t = svg.transition().duration(500);
const typeTotal = dataTypesTotal[i];
const typeDaily = dataTypesDaily[i];
const type = plotTotal ? typeTotal : typeDaily;
const filteredTimeseries = timeseries.filter((d) => !isNaN(d[type]));
const color = colors[i];
const yScale = yScales[i];
/* X axis */
svg
.select('.x-axis')
.style('transform', `translateY(${chartBottom}px)`)
.transition(t)
.call(xAxis);
svg.select('.x-axis2').transition(t).call(xAxis2, yScale);
/* Y axis */
svg
.select('.y-axis')
.style('transform', `translateX(${chartRight}px)`)
.transition(t)
.call(yAxis, yScale);
/* Path dots */
svg
.selectAll('.dot')
.data(filteredTimeseries, (d) => d.date)
.join((enter) =>
enter
.append('circle')
.attr('cy', chartBottom)
.attr('cx', (d) => xScale(d.date))
)
.attr('class', 'dot')
.attr('fill', color)
.attr('stroke', color)
.attr('r', 2)
.transition(t)
.attr('cx', (d) => xScale(d.date))
.attr('cy', (d) => yScale(d[type]));
if (!isNaN(timeseries[T - 1][type]))
focus[i]
.transition(t)
.attr('cx', (d) => xScale(d.date))
.attr('cy', (d) => yScale(d[type]))
.attr('opacity', 1);
else focus[i].transition(t).attr('opacity', 0);
if (plotTotal) {
/* TOTAL TRENDS */
svg.selectAll('.stem').remove();
const path = svg
.selectAll('.trend')
.data([[...filteredTimeseries].reverse()])
.join('path')
.attr('class', 'trend')
.attr('fill', 'none')
.attr('stroke', color + '99')
.attr('stroke-width', 4);
// HACK
// Path interpolation is non-trivial. Ideally, a custom path tween
// function should be defined which takes care that old path dots
// transition synchronously along with the path transition. This hack
// simulates that behaviour.
if (path.attr('d')) {
const n = path.node().getTotalLength();
const p = path.node().getPointAtLength(n);
// Append points at end of path for better interpolation
path.attr(
'd',
() => path.attr('d') + `L${p.x},${p.y}`.repeat(3 * T)
);
}
path
.transition(t)
.attr('opacity', plotTotal ? 1 : 0)
.attr(
'd',
d3
.line()
.x((d) => xScale(d.date))
.y((d) => yScale(d[typeTotal]))
.curve(d3.curveMonotoneX)
);
// Using d3-interpolate-path
// .attrTween('d', function (d) {
// var previous = path.attr('d');
// var current = line(d);
// return interpolatePath(previous, current);
// });
} else {
/* DAILY TRENDS */
svg.selectAll('.trend').remove();
svg
.selectAll('.stem')
.data(filteredTimeseries, (d) => d.date)
.join((enter) =>
enter
.append('line')
.attr('x1', (d) => xScale(d.date))
.attr('y1', chartBottom)
.attr('x2', (d) => xScale(d.date))
.attr('y2', chartBottom)
)
.attr('class', 'stem')
.style('stroke', color + '99')
.style('stroke-width', 4)
.transition(t)
.attr('x1', (d) => xScale(d.date))
.attr('y1', yScale(0))
.attr('x2', (d) => xScale(d.date))
.attr('y2', (d) => yScale(d[typeDaily]));
}
svg
.on('mousemove', mousemove)
.on('touchmove', mousemove)
.on('mouseout', mouseout)
.on('touchend', mouseout);
});
},
[chartType, dimensions, logMode, mode]
);
useEffect(() => {
if (timeseries.length > 1) {
graphData(timeseries);
}
}, [timeseries, graphData]);
const dateStr = datapoint.date ? format(datapoint.date, 'dd MMMM') : '';
const chartKey1 = chartType === 1 ? 'totalconfirmed' : 'dailyconfirmed';
const chartKey2 = chartType === 1 ? 'totalactive' : 'dailyactive';
const chartKey3 = chartType === 1 ? 'totalrecovered' : 'dailyrecovered';
const chartKey4 = chartType === 1 ? 'totaldeceased' : 'dailydeceased';
const chartKey5 = chartType === 1 ? 'totaltested' : 'dailytested';
// Function for calculate increased/decreased count for each type of data
const currentStatusCount = (chartType) => {
if (timeseries.length <= 0 || index <= 0 || index >= timeseries.length)
return '';
const currentDiff =
timeseries[index][chartType] - timeseries[index - 1][chartType];
const formatedDiff = formatNumber(currentDiff);
return currentDiff >= 0 ? `+${formatedDiff}` : formatedDiff;
};
return (
<React.Fragment>
<div className="TimeSeries fadeInUp" style={{animationDelay: '2.7s'}}>
<div className="svg-parent" ref={wrapperRef}>
<div className="stats">
<h5 className={`${!moving ? 'title' : ''}`}>{t('Confirmed')}</h5>
<h5 className={`${moving ? 'title' : ''}`}>{`${dateStr}`}</h5>
<div className="stats-bottom">
<h2>{formatNumber(datapoint[chartKey1])}</h2>
<h6>{currentStatusCount(chartKey1)}</h6>
</div>
</div>
<svg ref={svgRef1} preserveAspectRatio="xMidYMid meet">
<g className="x-axis" />
<g className="x-axis2" />
<g className="y-axis" />
</svg>
</div>
<div className="svg-parent is-blue">
<div className="stats is-blue">
<h5 className={`${!moving ? 'title' : ''}`}>{t('Active')}</h5>
<h5 className={`${moving ? 'title' : ''}`}>{`${dateStr}`}</h5>
<div className="stats-bottom">
<h2>{formatNumber(datapoint[chartKey2])}</h2>
<h6>{currentStatusCount(chartKey2)}</h6>
</div>
</div>
<svg ref={svgRef2} preserveAspectRatio="xMidYMid meet">
<g className="x-axis" />
<g className="x-axis2" />
<g className="y-axis" />
</svg>
</div>
<div className="svg-parent is-green">
<div className="stats is-green">
<h5 className={`${!moving ? 'title' : ''}`}>{t('Recovered')}</h5>
<h5 className={`${moving ? 'title' : ''}`}>{`${dateStr}`}</h5>
<div className="stats-bottom">
<h2>{formatNumber(datapoint[chartKey3])}</h2>
<h6>{currentStatusCount(chartKey3)}</h6>
</div>
</div>
<svg ref={svgRef3} preserveAspectRatio="xMidYMid meet">
<g className="x-axis" />
<g className="x-axis2" />
<g className="y-axis" />
</svg>
</div>
<div className="svg-parent is-gray">
<div className="stats is-gray">
<h5 className={`${!moving ? 'title' : ''}`}>{t('Deceased')}</h5>
<h5 className={`${moving ? 'title' : ''}`}>{`${dateStr}`}</h5>
<div className="stats-bottom">
<h2>{formatNumber(datapoint[chartKey4])}</h2>
<h6>{currentStatusCount(chartKey4)}</h6>
</div>
</div>
<svg ref={svgRef4} preserveAspectRatio="xMidYMid meet">
<g className="x-axis" />
<g className="x-axis2" />
<g className="y-axis" />
</svg>
</div>
<div className="svg-parent is-purple">
<div className="stats is-purple">
<h5 className={`${!moving ? 'title' : ''}`}>
{t('Tested')} {isTotal ? testedToolTip : ''}
</h5>
<h5 className={`${moving ? 'title' : ''}`}>{`${dateStr}`}</h5>
<div className="stats-bottom">
<h2>{formatNumber(datapoint[chartKey5])}</h2>
<h6>{currentStatusCount(chartKey5)}</h6>
</div>
</div>
<svg ref={svgRef5} preserveAspectRatio="xMidYMid meet">
<g className="x-axis" />
<g className="x-axis2" />
<g className="y-axis" />
</svg>
</div>
</div>
<div className="pills">
<button
type="button"
onClick={() => setLastDaysCount(Infinity)}
className={lastDaysCount === Infinity ? 'selected' : ''}
>
{t('Beginning')}
</button>
<button
type="button"
onClick={() => setLastDaysCount(30)}
className={lastDaysCount === 30 ? 'selected' : ''}
aria-label="1 month"
>
{`1 ${t('Month')}`}
</button>
<button
type="button"
onClick={() => setLastDaysCount(14)}
className={lastDaysCount === 14 ? 'selected' : ''}
aria-label="14 days"
>
{`2 ${t('Weeks')}`}
</button>
</div>
<div className="alert">
<Icon.AlertOctagon />
<div className="alert-right">
{t('Tested chart is independent of uniform scaling')}
</div>
</div>
</React.Fragment>
);
}
Example #9
Source File: Minigraphs.js From covid19india-react with MIT License | 4 votes |
function Minigraphs({timeseries, date: timelineDate}) {
const refs = useRef([]);
const endDate = timelineDate || getIndiaDateYesterdayISO();
let [wrapperRef, {width}] = useMeasure();
width = Math.min(width, maxWidth);
const dates = useMemo(() => {
const pastDates = Object.keys(timeseries || {}).filter(
(date) => date <= endDate
);
const lastDate = pastDates[pastDates.length - 1];
const cutOffDateLower = formatISO(
subDays(parseIndiaDate(lastDate), MINIGRAPH_LOOKBACK_DAYS),
{representation: 'date'}
);
return pastDates.filter((date) => date >= cutOffDateLower);
}, [endDate, timeseries]);
const getMinigraphStatistic = useCallback(
(date, statistic) => {
return getStatistic(timeseries?.[date], 'delta', statistic);
},
[timeseries]
);
useEffect(() => {
if (!width) return;
const T = dates.length;
const chartRight = width - margin.right;
const chartBottom = height - margin.bottom;
const xScale = scaleTime()
.clamp(true)
.domain([
parseIndiaDate(dates[0] || endDate),
parseIndiaDate(dates[T - 1]) || endDate,
])
.range([margin.left, chartRight]);
refs.current.forEach((ref, index) => {
const svg = select(ref);
const statistic = LEVEL_STATISTICS[index];
const color = STATISTIC_CONFIGS[statistic].color;
const dailyMaxAbs = max(dates, (date) =>
Math.abs(getMinigraphStatistic(date, statistic))
);
const yScale = scaleLinear()
.clamp(true)
.domain([-dailyMaxAbs, dailyMaxAbs])
.range([chartBottom, margin.top]);
const linePath = line()
.curve(curveMonotoneX)
.x((date) => xScale(parseIndiaDate(date)))
.y((date) => yScale(getMinigraphStatistic(date, statistic)));
let pathLength;
svg
.selectAll('path')
.data(T ? [dates] : [])
.join(
(enter) =>
enter
.append('path')
.attr('fill', 'none')
.attr('stroke', color + '99')
.attr('stroke-width', 2.5)
.attr('d', linePath)
.attr('stroke-dasharray', function () {
return (pathLength = this.getTotalLength());
})
.call((enter) =>
enter
.attr('stroke-dashoffset', pathLength)
.transition()
.delay(100)
.duration(2500)
.attr('stroke-dashoffset', 0)
),
(update) =>
update
.attr('stroke-dasharray', null)
.transition()
.duration(500)
.attrTween('d', function (date) {
const previous = select(this).attr('d');
const current = linePath(date);
return interpolatePath(previous, current);
})
.selection()
);
svg
.selectAll('circle')
.data(T ? [dates[T - 1]] : [])
.join(
(enter) =>
enter
.append('circle')
.attr('fill', color)
.attr('r', 2.5)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
.style('opacity', 0)
.call((enter) =>
enter
.transition()
.delay(2100)
.duration(500)
.style('opacity', 1)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
),
(update) =>
update
.transition()
.duration(500)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
.style('opacity', 1)
.selection()
);
});
}, [endDate, dates, width, getMinigraphStatistic]);
return (
<div className="Minigraph">
{LEVEL_STATISTICS.map((statistic, index) => (
<div
key={statistic}
className={classnames('svg-parent')}
ref={index === 0 ? wrapperRef : null}
style={{width: `calc(${100 / LEVEL_STATISTICS.length}%)`}}
>
<svg
ref={(el) => {
refs.current[index] = el;
}}
preserveAspectRatio="xMidYMid meet"
width={width}
height={height}
/>
</div>
))}
</div>
);
}
Example #10
Source File: StateMeta.js From covid19india-react with MIT License | 4 votes |
function StateMeta({stateCode, data, timeseries}) {
const {t} = useTranslation();
const confirmedPerLakh = getStatistic(data[stateCode], 'total', 'confirmed', {
normalizedByPopulationPer: 'lakh',
});
const testPerLakh = getStatistic(data[stateCode], 'total', 'tested', {
normalizedByPopulationPer: 'lakh',
});
const totalConfirmedPerLakh = getStatistic(data['TT'], 'total', 'confirmed', {
normalizedByPopulationPer: 'lakh',
});
const activePercent = getStatistic(data[stateCode], 'total', 'activeRatio');
const recoveryPercent = getStatistic(
data[stateCode],
'total',
'recoveryRatio'
);
const deathPercent = getStatistic(data[stateCode], 'total', 'cfr');
// Show TPR for week preceeding last updated date
const pastDates = Object.keys(timeseries || {}).filter(
(date) => date <= getIndiaDateYesterdayISO()
);
const lastDate = pastDates[pastDates.length - 1];
const prevWeekDate = formatISO(subDays(parseIndiaDate(lastDate), 6));
const tprWeek = getStatistic(timeseries?.[lastDate], 'delta', 'tpr', {
movingAverage: true,
});
return (
<>
<div className="StateMeta population">
<div className="meta-item population">
<h3>{t('Population')}</h3>
<h1>{formatNumber(data[stateCode]?.meta?.population)}</h1>
</div>
<div className="alert">
<Compass />
<div className="alert-right">
{t('Based on 2019 population projection by NCP')}
<a
href="https://nhm.gov.in/New_Updates_2018/Report_Population_Projection_2019.pdf"
target="_noblank"
>
report
</a>
</div>
</div>
</div>
<div className="StateMeta">
<StateMetaCard
className="confirmed"
title={t('Confirmed Per Lakh')}
statistic={formatNumber(confirmedPerLakh)}
total={formatNumber(totalConfirmedPerLakh)}
formula={
<>
{`${1e5} x `}
<Fraction
numerator={t('Total confirmed cases')}
denominator={t('Total population')}
/>
</>
}
description={`
~${formatNumber(confirmedPerLakh, 'long')} ${t(
'out of every lakh people in'
)} ${STATE_NAMES[stateCode]} ${t(
'have tested positive for the virus.'
)}
`}
/>
<StateMetaCard
className="active"
title={t('Active Ratio')}
statistic={`${formatNumber(activePercent, '%')}`}
formula={
<>
{'100 x '}
<Fraction
numerator={t('Total active cases right now')}
denominator={t('Total confirmed cases')}
/>
</>
}
description={
activePercent > 0
? `${t('For every 100 confirmed cases')}, ~${formatNumber(
activePercent,
'long'
)} ${t('are currently infected.')}`
: t('Currently, there are no active cases in this state.')
}
/>
<StateMetaCard
className="recovery"
title={t('Recovery Ratio')}
statistic={`${formatNumber(recoveryPercent, '%')}`}
formula={
<>
{'100 x '}
<Fraction
numerator={t('Total recovered cases')}
denominator={t('Total confirmed cases')}
/>
</>
}
description={
recoveryPercent > 0
? `${t('For every 100 confirmed cases')}, ~${formatNumber(
recoveryPercent,
'long'
)} ${t('have recovered from the virus.')}`
: t('Unfortunately, there are no recoveries in this state yet.')
}
/>
<StateMetaCard
className="mortality"
title={t('Case Fatality Ratio')}
statistic={`${formatNumber(deathPercent, '%')}`}
formula={
<>
{'100 x '}
<Fraction
numerator={t('Total deaths')}
denominator={t('Total confirmed cases')}
/>
</>
}
description={
deathPercent > 0
? `${t('For every 100 confirmed cases')}, ~${formatNumber(
deathPercent,
'long'
)} ${t('have unfortunately passed away from the virus.')}`
: t(
'Fortunately, no one has passed away from the virus in this state.'
)
}
/>
<StateMetaCard
className="tpr"
title={t('Test Positivity Ratio')}
statistic={tprWeek > 0 ? `${formatNumber(tprWeek, '%')}` : '-'}
formula={
<>
{'100 x '}
<Fraction
numerator={t('Confirmed cases last week')}
denominator={t('Samples tested last week')}
/>
</>
}
date={`${formatDate(prevWeekDate, 'dd MMM')} - ${formatDate(
lastDate,
'dd MMM'
)}`}
description={
tprWeek > 0
? `${t('In the last one week,')} ${formatNumber(tprWeek, '%')}
${t('of samples tested came back positive.')}`
: t('No tested sample came back positive in last one week.')
}
/>
<StateMetaCard
className="tpl"
title={t('Tests Per Lakh')}
statistic={`${formatNumber(testPerLakh)}`}
formula={
<>
{`${1e5} x `}
<Fraction
numerator={t('Total samples tested')}
denominator={t('Total population')}
/>
</>
}
date={
testPerLakh && data[stateCode]?.meta?.tested?.date
? `${t('As of')} ${formatLastUpdated(
data[stateCode].meta.tested.date
)} ${t('ago')}`
: ''
}
description={
testPerLakh > 0
? `${t('For every lakh people in')} ${STATE_NAMES[stateCode]},
~${formatNumber(testPerLakh, 'long')} ${t(
'samples were tested.'
)}`
: t('No tests have been conducted in this state yet.')
}
/>
</div>
</>
);
}
Example #11
Source File: TimeseriesExplorer.js From covid19india-react with MIT License | 4 votes |
function TimeseriesExplorer({
stateCode,
timeseries,
date: timelineDate,
regionHighlighted,
setRegionHighlighted,
anchor,
setAnchor,
expandTable = false,
hideVaccinated = false,
noRegionHighlightedDistrictData,
}) {
const {t} = useTranslation();
const [lookback, setLookback] = useLocalStorage('timeseriesLookbackDays', 90);
const [chartType, setChartType] = useLocalStorage('chartType', 'delta');
const [isUniform, setIsUniform] = useLocalStorage('isUniform', false);
const [isLog, setIsLog] = useLocalStorage('isLog', false);
const [isMovingAverage, setIsMovingAverage] = useLocalStorage(
'isMovingAverage',
false
);
const stateCodeDateRange = Object.keys(timeseries?.[stateCode]?.dates || {});
const beginningDate =
stateCodeDateRange[0] || timelineDate || getIndiaDateYesterdayISO();
const endDate = min([
stateCodeDateRange[stateCodeDateRange.length - 1],
timelineDate || getIndiaDateYesterdayISO(),
]);
const [brushSelectionEnd, setBrushSelectionEnd] = useState(endDate);
useEffect(() => {
setBrushSelectionEnd(endDate);
}, [endDate]);
const brushSelectionStart =
lookback !== null
? formatISO(subDays(parseIndiaDate(brushSelectionEnd), lookback), {
representation: 'date',
})
: beginningDate;
const explorerElement = useRef();
const isVisible = useIsVisible(explorerElement, {once: true});
const {width} = useWindowSize();
const selectedRegion = useMemo(() => {
if (timeseries?.[regionHighlighted.stateCode]?.districts) {
return {
stateCode: regionHighlighted.stateCode,
districtName: regionHighlighted.districtName,
};
} else {
return {
stateCode: regionHighlighted.stateCode,
districtName: null,
};
}
}, [timeseries, regionHighlighted.stateCode, regionHighlighted.districtName]);
const selectedTimeseries = useMemo(() => {
if (selectedRegion.districtName) {
return timeseries?.[selectedRegion.stateCode]?.districts?.[
selectedRegion.districtName
]?.dates;
} else {
return timeseries?.[selectedRegion.stateCode]?.dates;
}
}, [timeseries, selectedRegion.stateCode, selectedRegion.districtName]);
const regions = useMemo(() => {
const states = Object.keys(timeseries || {})
.filter((code) => code !== stateCode)
.sort((code1, code2) =>
STATE_NAMES[code1].localeCompare(STATE_NAMES[code2])
)
.map((code) => {
return {
stateCode: code,
districtName: null,
};
});
const districts = Object.keys(timeseries || {}).reduce((acc1, code) => {
return [
...acc1,
...Object.keys(timeseries?.[code]?.districts || {}).reduce(
(acc2, districtName) => {
return [
...acc2,
{
stateCode: code,
districtName: districtName,
},
];
},
[]
),
];
}, []);
return [
{
stateCode: stateCode,
districtName: null,
},
...states,
...districts,
];
}, [timeseries, stateCode]);
const dropdownRegions = useMemo(() => {
if (
regions.find(
(region) =>
region.stateCode === regionHighlighted.stateCode &&
region.districtName === regionHighlighted.districtName
)
)
return regions;
return [
...regions,
{
stateCode: regionHighlighted.stateCode,
districtName: regionHighlighted.districtName,
},
];
}, [regionHighlighted.stateCode, regionHighlighted.districtName, regions]);
const dates = useMemo(
() =>
Object.keys(selectedTimeseries || {}).filter((date) => date <= endDate),
[selectedTimeseries, endDate]
);
const brushSelectionDates = useMemo(
() =>
dates.filter(
(date) => brushSelectionStart <= date && date <= brushSelectionEnd
),
[dates, brushSelectionStart, brushSelectionEnd]
);
const handleChange = useCallback(
({target}) => {
setRegionHighlighted(JSON.parse(target.value));
},
[setRegionHighlighted]
);
const resetDropdown = useCallback(() => {
setRegionHighlighted({
stateCode: stateCode,
districtName: null,
});
}, [stateCode, setRegionHighlighted]);
const statistics = useMemo(
() =>
TIMESERIES_STATISTICS.filter(
(statistic) =>
(!(STATISTIC_CONFIGS[statistic]?.category === 'vaccinated') ||
!hideVaccinated) &&
// (chartType === 'total' || statistic !== 'active') &&
(chartType === 'delta' || statistic !== 'tpr')
),
[chartType, hideVaccinated]
);
return (
<div
className={classnames(
'TimeseriesExplorer fadeInUp',
{
stickied: anchor === 'timeseries',
},
{expanded: expandTable}
)}
style={{
display:
anchor && anchor !== 'timeseries' && (!expandTable || width < 769)
? 'none'
: '',
}}
ref={explorerElement}
>
<div className="timeseries-header">
<div
className={classnames('anchor', 'fadeInUp', {
stickied: anchor === 'timeseries',
})}
style={{
display: expandTable && width >= 769 ? 'none' : '',
}}
onClick={
setAnchor &&
setAnchor.bind(this, anchor === 'timeseries' ? null : 'timeseries')
}
>
<PinIcon />
</div>
<h1>{t('Spread Trends')}</h1>
<div className="tabs">
{Object.entries(TIMESERIES_CHART_TYPES).map(
([ctype, value], index) => (
<div
className={`tab ${chartType === ctype ? 'focused' : ''}`}
key={ctype}
onClick={setChartType.bind(this, ctype)}
>
<h4>{t(value)}</h4>
</div>
)
)}
</div>
<div className="timeseries-options">
<div className="scale-modes">
<label className="main">{`${t('Scale Modes')}:`}</label>
<div className="timeseries-mode">
<label htmlFor="timeseries-mode">{t('Uniform')}</label>
<input
id="timeseries-mode"
type="checkbox"
className="switch"
checked={isUniform}
aria-label={t('Checked by default to scale uniformly.')}
onChange={setIsUniform.bind(this, !isUniform)}
/>
</div>
<div
className={`timeseries-mode ${
chartType !== 'total' ? 'disabled' : ''
}`}
>
<label htmlFor="timeseries-logmode">{t('Logarithmic')}</label>
<input
id="timeseries-logmode"
type="checkbox"
checked={chartType === 'total' && isLog}
className="switch"
disabled={chartType !== 'total'}
onChange={setIsLog.bind(this, !isLog)}
/>
</div>
</div>
<div
className={`timeseries-mode ${
chartType === 'total' ? 'disabled' : ''
} moving-average`}
>
<label htmlFor="timeseries-moving-average">
{t('7 day Moving Average')}
</label>
<input
id="timeseries-moving-average"
type="checkbox"
checked={chartType === 'delta' && isMovingAverage}
className="switch"
disabled={chartType !== 'delta'}
onChange={setIsMovingAverage.bind(this, !isMovingAverage)}
/>
</div>
</div>
</div>
{dropdownRegions && (
<div className="state-selection">
<div className="dropdown">
<select
value={JSON.stringify(selectedRegion)}
onChange={handleChange}
>
{dropdownRegions
.filter(
(region) =>
STATE_NAMES[region.stateCode] !== region.districtName
)
.map((region) => {
return (
<option
value={JSON.stringify(region)}
key={`${region.stateCode}-${region.districtName}`}
>
{region.districtName
? t(region.districtName)
: t(STATE_NAMES[region.stateCode])}
</option>
);
})}
</select>
</div>
<div className="reset-icon" onClick={resetDropdown}>
<ReplyIcon />
</div>
</div>
)}
{isVisible && (
<Suspense fallback={<TimeseriesLoader />}>
<Timeseries
timeseries={selectedTimeseries}
regionHighlighted={selectedRegion}
dates={brushSelectionDates}
{...{
statistics,
endDate,
chartType,
isUniform,
isLog,
isMovingAverage,
noRegionHighlightedDistrictData,
}}
/>
<TimeseriesBrush
timeseries={selectedTimeseries}
regionHighlighted={selectedRegion}
currentBrushSelection={[brushSelectionStart, brushSelectionEnd]}
animationIndex={statistics.length}
{...{dates, endDate, lookback, setBrushSelectionEnd, setLookback}}
/>
</Suspense>
)}
{!isVisible && <div style={{height: '50rem'}} />}
<div
className="pills fadeInUp"
style={{animationDelay: `${(1 + statistics.length) * 250}ms`}}
>
{TIMESERIES_LOOKBACK_DAYS.map((numDays) => (
<button
key={numDays}
type="button"
className={classnames({
selected: numDays === lookback,
})}
onClick={setLookback.bind(this, numDays)}
>
{numDays !== null ? `${numDays} ${t('days')}` : t('Beginning')}
</button>
))}
</div>
</div>
);
}