private void updateTimeSeriesMeta(CategoryDbTable.Row row, boolean disable) {
    TimeSeries ts = getSeriesByIdNonlocking(row.getId());

    if (ts == null) {
      if (mDefaultPainter == null) {
        TimeSeriesPainter p = new TimeSeriesPainter.Default();
        ts = new TimeSeries(row, mHistory, mSmoothing, p);
      } else {
        ts = new TimeSeries(row, mHistory, mSmoothing, mDefaultPainter);
      }
      mSeries.add(ts);
      mDatapointCache.addCacheableCategory(row.getId(), mHistory);
    }

    ts.setDbRow(row);
    setSeriesInterpolator(ts, row.getInterpolation());

    if (row.getSynthetic() == true) {
      Formula formula = mFormulaCache.getFormula(Long.valueOf(row.getId()));
      if (formula == null) formula = new Formula();
      formula.setFormula(row.getFormula());
      mFormulaCache.setFormula(row.getId(), formula);
    }

    if (disable) ts.setEnabled(false);

    setDependents(ts);
    setDependees(ts);
  }
  public synchronized void updateCategoryTrend(long catId) {
    String trendStr = "trend_unknown";
    float stdDev = 0.0f;
    float lastTrend = 0.0f;
    float newTrend = 0.0f;

    gatherLatestDatapointsLocking(catId, mHistory);
    TimeSeries ts = getSeriesByIdLocking(catId);
    if (ts == null) return;

    if (ts.getDbRow().getSynthetic() == true) return;

    lastTrend = ts.getTrendStats().mTrendPrev;
    newTrend = ts.getTrendStats().mTrend;
    stdDev = ts.getValueStats().mStdDev;

    TrendState state =
        Number.getTrendState(lastTrend, newTrend, ts.getDbRow().getGoal(), mSensitivity, stdDev);
    trendStr = Number.mapTrendStateToString(state);

    mDbh.updateCategoryTrend(catId, trendStr, newTrend);

    if (ts.getDependees() != null && ts.getDependees().size() > 0) {
      for (int i = 0; i < ts.getDependees().size(); i++) {
        TimeSeries dependee = ts.getDependees().get(i);

        for (int j = 0; j < dependee.getDependents().size(); j++) {
          TimeSeries tmp = dependee.getDependents().get(j);
          if (tmp != null) gatherLatestDatapointsLocking(tmp.getDbRow().getId(), mHistory);
        }

        Formula formula = mFormulaCache.getFormula(dependee.getDbRow().getId());
        ArrayList<Datapoint> calculated = formula.apply(dependee.getDependents());
        dependee.setDatapoints(null, calculated, null, true);

        lastTrend = dependee.getTrendStats().mTrendPrev;
        newTrend = dependee.getTrendStats().mTrend;
        stdDev = dependee.getValueStats().mStdDev;

        state =
            Number.getTrendState(
                lastTrend, newTrend, dependee.getDbRow().getGoal(), mSensitivity, stdDev);
        trendStr = Number.mapTrendStateToString(state);

        mDbh.updateCategoryTrend(dependee.getDbRow().getId(), trendStr, newTrend);
      }
    }
  }
  private void setDependees(TimeSeries ts) {
    ts.getDependees().clear();
    for (int i = 0; i < mSeries.size(); i++) {
      TimeSeries dependee = getSeries(i);
      if (ts == null || dependee == null || ts == dependee) continue;

      if (dependee.getDbRow().getSynthetic() == true) {
        Formula formula = mFormulaCache.getFormula(dependee.getDbRow().getId());
        ArrayList<String> names = formula.getDependentNames();

        if (names != null && names.contains(ts.getDbRow().getCategoryName()))
          ts.addDependee(dependee);
      }
    }

    return;
  }
  private void setDependents(TimeSeries synth) {
    if (synth.getDbRow().getSynthetic() == false) return;

    Formula formula = mFormulaCache.getFormula(synth.getDbRow().getId());
    ArrayList<String> names = formula.getDependentNames();
    if (names == null) return;

    synth.getDependents().clear();
    for (int i = 0; i < mSeries.size(); i++) {
      TimeSeries ts = mSeries.get(i);
      if (ts == null || ts == synth) continue;

      if (names.contains(ts.getDbRow().getCategoryName())) synth.addDependent(ts);
    }

    return;
  }
  private void generateSynthetic(TimeSeries synth) {
    Formula formula = mFormulaCache.getFormula(synth.getDbRow().getId());

    long ms;
    long firstVisibleMs = Long.MAX_VALUE;
    long lastVisibleMs = Long.MIN_VALUE;

    for (int j = 0; j < synth.getDependents().size(); j++) {
      TimeSeries ts = synth.getDependents().get(j);
      List<Datapoint> range = ts.getVisible();

      if (range != null) {
        ms = range.get(0).mMillis;
        if (ms < firstVisibleMs) firstVisibleMs = ms;
        ms = range.get(range.size() - 1).mMillis;
        if (ms > lastVisibleMs) lastVisibleMs = ms;
      }
    }

    ArrayList<Datapoint> calculated = formula.apply(synth.getDependents());
    ArrayList<Datapoint> pre = new ArrayList<Datapoint>();
    ArrayList<Datapoint> visible = new ArrayList<Datapoint>();
    ArrayList<Datapoint> post = new ArrayList<Datapoint>();

    for (int j = 0; j < calculated.size(); j++) {
      Datapoint d = calculated.get(j);
      d.mCatId = synth.getDbRow().getId();
      d.mSynthetic = true;
      if (d.mMillis < firstVisibleMs) pre.add(d);
      else if (d.mMillis <= lastVisibleMs) visible.add(d);
      else post.add(d);
    }

    pre = aggregateDatapoints(pre, synth.getDbRow().getType());
    visible = aggregateDatapoints(visible, synth.getDbRow().getType());
    post = aggregateDatapoints(post, synth.getDbRow().getType());

    synth.setDatapoints(pre, visible, post, true);
  }