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 vote down vote up
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 vote down vote up
getIndiaDateYesterday = () => {
  return subDays(getIndiaDate(), 1);
}
Example #3
Source File: Calendar.js    From umami with MIT License 5 votes vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
  );
}