示例#1
0
    @Override
    public String getAccessibleName() {
      String name = super.getAccessibleName();

      if (seqs.size() > 0 && seqs.get(0).size > 0) {
        String keyValueList = "";
        for (Sequence seq : seqs) {
          if (seq.isPlotted) {
            String value = "null";
            if (seq.size > 0) {
              if (unit == Unit.BYTES) {
                value = Resources.format(Messages.SIZE_BYTES, seq.value(seq.size - 1));
              } else {
                value =
                    getFormattedValue(seq.value(seq.size - 1), false)
                        + ((unit == Unit.PERCENT) ? "%" : "");
              }
            }
            // Assume format string ends with newline
            keyValueList +=
                Resources.format(Messages.PLOTTER_ACCESSIBLE_NAME_KEY_AND_VALUE, seq.key, value);
          }
        }
        name += "\n" + keyValueList + ".";
      } else {
        name += "\n" + Messages.PLOTTER_ACCESSIBLE_NAME_NO_DATA;
      }
      return name;
    }
 /**
  * Get the list of ids of sequences in this sequence database
  *
  * @return the list of sequence ids.
  */
 public Set<Integer> getSequenceIDs() {
   Set<Integer> set = new HashSet<Integer>();
   for (Sequence sequence : getSequences()) {
     set.add(sequence.getId());
   }
   return set;
 }
示例#3
0
  public void go() {
    setUpGui();

    try {

      Sequencer sequencer = MidiSystem.getSequencer();
      sequencer.open();
      // make a sequencer and open
      sequencer.addControllerEventListener(m1, new int[] {127});
      Sequence seq = new Sequence(Sequence.PPQ, 4);
      Track track = seq.createTrack();

      int r = 0;
      for (int i = 0; i < 300; i += 4) {

        r = (int) ((Math.random() * 50) + 1);
        track.add(makeEvent(144, 1, r, 100, i));
        track.add(makeEvent(176, 1, 127, 0, i));
        track.add(makeEvent(128, 1, r, 100, i + 2));
      } // end loop

      sequencer.setSequence(seq);
      sequencer.start();
      sequencer.setTempoInBPM(120);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  } // close method
 /**
  * Get a string representation of this sequence database
  *
  * @return the string representation
  */
 @Override
 public String toString() {
   StringBuilder r = new StringBuilder();
   for (Sequence sequence : sequences) {
     r.append(sequence.getId());
     r.append(":  ");
     r.append(sequence.toString());
     r.append('\n');
   }
   return r.toString();
 }
示例#5
0
  public void createSequence(String key, String name, Color color, boolean isPlotted) {
    Sequence seq = getSequence(key);
    if (seq == null) {
      seq = new Sequence(key);
    }
    seq.name = name;
    seq.color = (color != null) ? color : defaultColor;
    seq.isPlotted = isPlotted;

    seqs.add(seq);
  }
 public Sequence getSequence(InputStream inputStream)
     throws InvalidMidiDataException, IOException {
   MidiFileFormat midiFileFormat = getMidiFileFormat(inputStream);
   Sequence sequence =
       new Sequence(midiFileFormat.getDivisionType(), midiFileFormat.getResolution());
   DataInputStream dataInputStream = new DataInputStream(inputStream);
   int nNumTracks = ((TMidiFileFormat) midiFileFormat).getTrackCount();
   for (int nTrack = 0; nTrack < nNumTracks; nTrack++) {
     Track track = sequence.createTrack();
     readTrack(dataInputStream, track);
   }
   return sequence;
 }
示例#7
0
文件: MSound.java 项目: hoop21/metr
 public void go() {
   try {
     sq.open();
     Sequence seq = new Sequence(Sequence.PPQ, 4);
     Track track = seq.createTrack();
     track.add(addNote(144, 9, 56, 100, 1));
     track.add(addNote(128, 9, 56, 100, 4));
     sq.setSequence(seq);
     sq.setTempoInBPM(tempo);
     sq.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
     sq.start();
   } catch (Exception ex) {
     ex.printStackTrace();
   }
 }
示例#8
0
  // Called on EDT
  public void propertyChange(PropertyChangeEvent ev) {
    String prop = ev.getPropertyName();

    if (prop == JConsoleContext.CONNECTION_STATE_PROPERTY) {
      ConnectionState newState = (ConnectionState) ev.getNewValue();

      switch (newState) {
        case DISCONNECTED:
          synchronized (this) {
            long time = System.currentTimeMillis();
            times.add(time);
            for (Sequence seq : seqs) {
              seq.add(Long.MIN_VALUE);
            }
          }
          break;
      }
    }
  }
示例#9
0
  private void saveDataToFile(File file) {
    try {
      PrintStream out = new PrintStream(new FileOutputStream(file));

      // Print header line
      out.print("Time");
      for (Sequence seq : seqs) {
        out.print("," + seq.name);
      }
      out.println();

      // Print data lines
      if (seqs.size() > 0 && seqs.get(0).size > 0) {
        for (int i = 0; i < seqs.get(0).size; i++) {
          double excelTime = toExcelTime(times.time(i));
          out.print(String.format(Locale.ENGLISH, "%.6f", excelTime));
          for (Sequence seq : seqs) {
            out.print("," + getFormattedValue(seq.value(i), false));
          }
          out.println();
        }
      }

      out.close();
      JOptionPane.showMessageDialog(
          this,
          Resources.format(
              Messages.FILE_CHOOSER_SAVED_FILE, file.getAbsolutePath(), file.length()));
    } catch (IOException ex) {
      String msg = ex.getLocalizedMessage();
      String path = file.getAbsolutePath();
      if (msg.startsWith(path)) {
        msg = msg.substring(path.length()).trim();
      }
      JOptionPane.showMessageDialog(
          this,
          Resources.format(Messages.FILE_CHOOSER_SAVE_FAILED_MESSAGE, path, msg),
          Messages.FILE_CHOOSER_SAVE_FAILED_TITLE,
          JOptionPane.ERROR_MESSAGE);
    }
  }
示例#10
0
  /**
   * Import a song into the MidiExporter to play or export.
   *
   * @param song The Song to convert to MIDI
   */
  public void importSong(Song song) {
    // Fetch important data from the song.
    // Meaning, metadata and the song itself.
    Beat[] beats = song.getBeatArray();
    this.song = song;

    // Create a track for each voice.
    int numVoices = song.getNumVoices();
    for (int voiceCount = 0; voiceCount < numVoices; voiceCount++) {
      sequence.createTrack();
    }

    // Iterate through each beat, adding each note to the corresponding
    // track.
    Track[] tracks = sequence.getTracks();
    for (int beat = 0; beat < beats.length; beat++) {
      Note[] firstHalf = beats[beat].getNotesFirstHalf();
      Note[] secondHalf = beats[beat].getNotesSecondHalf();
      // Iterate through each note in the beat, adding it to the
      // corresponding track.
      for (int note = 0; note < firstHalf.length; note++) {
        if (firstHalf[note] == secondHalf[note]) {
          createNote(firstHalf[note], 2 * beat, 2, tracks[note]);
        } else {
          createNote(firstHalf[note], 2 * beat, 1, tracks[note]);
          createNote(secondHalf[note], 2 * beat + 1, 1, tracks[note]);
        } // if/else
      } // for
    } // for

    try {
      setUpSequencer();
    } catch (MidiUnavailableException ex) {
      System.out.println("Unable to set up sequencer");
      // do nothing
    }
  }
示例#11
0
  /**
   * It adds a sequence from an array of string that we have to interpret
   *
   * @param integers
   * @param sequenceID
   */
  public void addSequence(String[] integers, int sequenceID) {
    long timestamp = -1;
    Sequence sequence = new Sequence(sequences.size());
    sequence.setID(sequenceID);
    Itemset itemset = new Itemset();
    int inicio = 0;
    Map<Item, Boolean> counted = new HashMap<Item, Boolean>();

    for (int i = inicio; i < integers.length; i++) {
      if (integers[i].codePointAt(0) == '<') { // Timestamp
        String value = integers[i].substring(1, integers[i].length() - 1);
        timestamp = Long.parseLong(value);
        itemset.setTimestamp(timestamp);
      } else if (integers[i].equals("-1")) { // end of an itemset
        long time = itemset.getTimestamp() + 1;
        sequence.addItemset(itemset);
        itemset = new Itemset();
        itemset.setTimestamp(time);
      } else if (integers[i].equals("-2")) { // end of a sequence
        sequences.add(sequence);
      } else {
        // extract the value for an item
        Item item = itemFactory.getItem(Integer.parseInt(integers[i]));
        if (counted.get(item) == null) {
          counted.put(item, Boolean.TRUE);
          BitSet appearances = frequentItems.get(item);
          if (appearances == null) {
            appearances = new BitSet();
            frequentItems.put(item, appearances);
          }
          appearances.set(sequence.getId());
        }
        itemset.addItem(item);
      }
    }
  }
示例#12
0
  public boolean writeSequence(NoteList noteList) {

    this.noteList = noteList;

    toneMap = toneMapFrame.getToneMap();
    timeSet = toneMap.getTimeSet();
    pitchSet = toneMap.getPitchSet();
    timeRange = timeSet.getRange();
    pitchRange = pitchSet.getRange();

    if (!buildNoteSequence()) return false;

    try {
      sequence = new Sequence(Sequence.PPQ, 10);
    } catch (Exception ex) {
      ex.printStackTrace();
      return false;
    }

    track = sequence.createTrack();
    startTime = System.currentTimeMillis();

    // add a program change right at the beginning of
    // the track for the current instrument

    createEvent(PROGRAM, cc.program + 1, 1);

    for (int i = 0; i < noteSequence.size(); i++) {
      noteSequenceElement = noteSequence.get(i);
      if (noteSequenceElement.state == ON)
        if (!createEvent(NOTEON, noteSequenceElement.note, noteSequenceElement.tick)) return false;
      if (noteSequenceElement.state == OFF)
        if (!createEvent(NOTEOFF, noteSequenceElement.note, noteSequenceElement.tick)) return false;
    }
    return true;
  }
示例#13
0
  protected PartData retrieveEntryDetails(String userId, Entry entry) {
    // user must be able to read if not public entry
    if (!permissionsController.isPubliclyVisible(entry)) authorization.expectRead(userId, entry);

    PartData partData = ModelToInfoFactory.getInfo(entry);
    if (partData == null) return null;
    boolean hasSequence = sequenceDAO.hasSequence(entry.getId());

    partData.setHasSequence(hasSequence);
    boolean hasOriginalSequence = sequenceDAO.hasOriginalSequence(entry.getId());
    partData.setHasOriginalSequence(hasOriginalSequence);

    // permissions
    partData.setCanEdit(authorization.canWriteThoroughCheck(userId, entry));
    partData.setPublicRead(permissionsController.isPubliclyVisible(entry));

    // create audit event if not owner
    // todo : remote access check
    if (userId != null
        && authorization.getOwner(entry) != null
        && !authorization.getOwner(entry).equalsIgnoreCase(userId)) {
      try {
        Audit audit = new Audit();
        audit.setAction(AuditType.READ.getAbbrev());
        audit.setEntry(entry);
        audit.setUserId(userId);
        audit.setLocalUser(true);
        audit.setTime(new Date(System.currentTimeMillis()));
        auditDAO.create(audit);
      } catch (Exception e) {
        Logger.error(e);
      }
    }

    // retrieve more information about linked entries if any (default only contains id)
    if (partData.getLinkedParts() != null) {
      ArrayList<PartData> newLinks = new ArrayList<>();
      for (PartData link : partData.getLinkedParts()) {
        Entry linkedEntry = dao.get(link.getId());
        if (!authorization.canRead(userId, linkedEntry)) continue;

        link = ModelToInfoFactory.createTipView(linkedEntry);
        Sequence sequence = sequenceDAO.getByEntry(linkedEntry);
        if (sequence != null) {
          link.setBasePairCount(sequence.getSequence().length());
          link.setFeatureCount(sequence.getSequenceFeatures().size());
        }

        newLinks.add(link);
      }
      partData.getLinkedParts().clear();
      partData.getLinkedParts().addAll(newLinks);
    }

    // check if there is a parent available
    List<Entry> parents = dao.getParents(entry.getId());
    if (parents == null) return partData;

    for (Entry parent : parents) {
      if (!authorization.canRead(userId, parent)) continue;

      if (parent.getVisibility() != Visibility.OK.getValue()
          && !authorization.canWriteThoroughCheck(userId, entry)) continue;

      EntryType type = EntryType.nameToType(parent.getRecordType());
      PartData parentData = new PartData(type);
      parentData.setId(parent.getId());
      parentData.setName(parent.getName());
      parentData.setVisibility(Visibility.valueToEnum(parent.getVisibility()));
      partData.getParents().add(parentData);
    }

    return partData;
  }
示例#14
0
 public void setUseDashedTransitions(String key, boolean b) {
   Sequence seq = getSequence(key);
   if (seq != null) {
     seq.transitionStroke = b ? getDashedStroke() : null;
   }
 }
示例#15
0
 public void setIsPlotted(String key, boolean isPlotted) {
   Sequence seq = getSequence(key);
   if (seq != null) {
     seq.isPlotted = isPlotted;
   }
 }
示例#16
0
  public double playGetLength() {

    return sequence.getMicrosecondLength();
  }
示例#17
0
 public double getTickRate() {
   return ((double) (sequence.getResolution() * getBPM() / 60));
 }
示例#18
0
  private void dnaCommand(HttpServletRequest req, DazzleResponse resp, DazzleDataSource dds)
      throws IOException, DataSourceException, ServletException, DazzleException {

    DazzleReferenceSource drs = (DazzleReferenceSource) dds;

    List segments = DazzleTools.getSegments(dds, req, resp);
    if (segments.size() == 0) {
      throw new DazzleException(
          DASStatus.STATUS_BAD_COMMAND_ARGUMENTS, "No segments specified for dna command");
    }

    // Fetch and validate the requests.

    Map segmentResults = new HashMap();
    for (Iterator i = segments.iterator(); i.hasNext(); ) {
      Segment seg = (Segment) i.next();

      try {
        Sequence seq = drs.getSequence(seg.getReference());
        if (seq.getAlphabet() != DNATools.getDNA()) {
          throw new DazzleException(
              DASStatus.STATUS_SERVER_ERROR,
              "Sequence " + seg.toString() + " is not in the DNA alphabet");
        }
        if (seg.isBounded()) {
          if (seg.getMin() < 1 || seg.getMax() > seq.length()) {
            throw new DazzleException(
                DASStatus.STATUS_BAD_COORDS,
                "Segment " + seg.toString() + " doesn't fit sequence of length " + seq.length());
          }
        }
        segmentResults.put(seg, seq);
      } catch (NoSuchElementException ex) {
        throw new DazzleException(DASStatus.STATUS_BAD_REFERENCE, ex);
      } catch (DataSourceException ex) {
        throw new DazzleException(DASStatus.STATUS_SERVER_ERROR, ex);
      }
    }

    //
    // Looks okay -- generate the response document
    //

    XMLWriter xw = resp.startDasXML("DASDNA", "dasdna.dtd");

    try {
      xw.openTag("DASDNA");
      for (Iterator i = segmentResults.entrySet().iterator(); i.hasNext(); ) {
        Map.Entry me = (Map.Entry) i.next();
        Segment seg = (Segment) me.getKey();
        Sequence seq = (Sequence) me.getValue();

        xw.openTag("SEQUENCE");
        xw.attribute("id", seg.getReference());
        xw.attribute("version", drs.getLandmarkVersion(seg.getReference()));
        if (seg.isBounded()) {
          xw.attribute("start", "" + seg.getStart());
          xw.attribute("stop", "" + seg.getStop());
        } else {
          xw.attribute("start", "" + 1);
          xw.attribute("stop", "" + seq.length());
        }

        SymbolList syms = seq;
        if (seg.isBounded()) {
          syms = syms.subList(seg.getMin(), seg.getMax());
        }
        if (seg.isInverted()) {
          syms = DNATools.reverseComplement(syms);
        }

        xw.openTag("DNA");
        xw.attribute("length", "" + syms.length());

        for (int pos = 1; pos <= syms.length(); pos += 60) {
          int maxPos = Math.min(syms.length(), pos + 59);
          xw.println(syms.subStr(pos, maxPos));
        }

        xw.closeTag("DNA");
        xw.closeTag("SEQUENCE");
      }
      xw.closeTag("DASDNA");
      xw.close();
    } catch (Exception ex) {
      throw new DazzleException(ex, "Error writing DNA document");
    }
  }
示例#19
0
  /**
   * @param scene the scene to filter
   * @param filter the filter to use
   * @param containsFilter true to exclusively use the arguments or false to exclude filter
   *     arguments
   * @return the filteredSequence
   */
  public static Scene filterScene(Scene scene, String[] filter, boolean containsFilter) {

    Sequence[] originalSequences = scene.getSequences();
    List<Sequence> sequenceList = new ArrayList<Sequence>();
    List<Sequence> aggregatedList = new ArrayList<Sequence>();

    for (int i = 0; i < originalSequences.length; i++) {

      Sequence tmpSequence = originalSequences[i].filterSequence(filter, containsFilter);
      sequenceList.add(tmpSequence);
    }

    if (originalSequences.length != sequenceList.size()) {

      System.out.println("ALARM Ungleiche Größe!");
    }
    //		aggregatedList = sequenceList;

    Sequence tmpBaseSequence = sequenceList.get(0);

    for (int i = 1; i < sequenceList.size(); i++) {

      if (tmpBaseSequence.equalTo(sequenceList.get(i))) {

        tmpBaseSequence.setLastTimestamp(sequenceList.get(i).getLastTimestamp());
        tmpBaseSequence.setNumberOfFrames(
            tmpBaseSequence.getNumberOfFrames() + sequenceList.get(i).getNumberOfFrames());

        if (sequenceList.get(i).getConcatenatedTypeNames().length != 0) {

          String[] tmpTypes = sequenceList.get(i).getConcatenatedTypeNames();

          for (int j = 0; j < tmpTypes.length; j++) {

            // increase age from previousSequence by numberOfFrames
            tmpBaseSequence.setElementOfTypeToAgeMap(
                tmpTypes[j],
                sequenceList.get(i).getNumberOfFrames()
                    + tmpBaseSequence.getElementOfTypeToAgeMap(tmpTypes[j]));
          }
        }

      } else {

        aggregatedList.add(tmpBaseSequence);
        tmpBaseSequence = sequenceList.get(i);
      }
    }

    aggregatedList.add(tmpBaseSequence);

    /*
     * added a final sequence to set all types to dying
     */
    Sequence nextToLastSeq = aggregatedList.get(aggregatedList.size() - 1);
    Sequence lastSequence = new Sequence();
    lastSequence.setDyingTypes(nextToLastSeq.getConcatenatedTypeNames());
    lastSequence.setFirstTimestamp(nextToLastSeq.getFirstTimestamp());
    lastSequence.setLastTimestamp(nextToLastSeq.getLastTimestamp());
    lastSequence.setNumberOfFrames(1);
    lastSequence.setTypeToAgeMap(nextToLastSeq.getTypeToAgeMap());

    aggregatedList.add(lastSequence);

    Sequence[] filteredSequences = new Sequence[aggregatedList.size()];
    filteredSequences = aggregatedList.toArray(filteredSequences);

    Scene filteredScene =
        new Scene(
            filteredSequences, scene.getNumberOfFrames(), scene.getStartTime(), scene.getEndTime());

    return filteredScene;
  }
示例#20
0
 long getLastValue(String key) {
   Sequence seq = getSequence(key);
   return (seq != null && seq.size > 0) ? seq.value(seq.size - 1) : 0L;
 }
示例#21
0
  @Override
  public void paintComponent(Graphics g) {
    super.paintComponent(g);

    int width = getWidth() - rightMargin - leftMargin - 10;
    int height = getHeight() - topMargin - bottomMargin;
    if (width <= 0 || height <= 0) {
      // not enough room to paint anything
      return;
    }

    Color oldColor = g.getColor();
    Font oldFont = g.getFont();
    Color fg = getForeground();
    Color bg = getBackground();
    boolean bgIsLight = (bg.getRed() > 200 && bg.getGreen() > 200 && bg.getBlue() > 200);

    ((Graphics2D) g)
        .setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    if (smallFont == null) {
      smallFont = oldFont.deriveFont(9.0F);
    }

    r.x = leftMargin - 5;
    r.y = topMargin - 8;
    r.width = getWidth() - leftMargin - rightMargin;
    r.height = getHeight() - topMargin - bottomMargin + 16;

    if (border == null) {
      // By setting colors here, we avoid recalculating them
      // over and over.
      border =
          new BevelBorder(
              BevelBorder.LOWERED,
              getBackground().brighter().brighter(),
              getBackground().brighter(),
              getBackground().darker().darker(),
              getBackground().darker());
    }

    border.paintBorder(this, g, r.x, r.y, r.width, r.height);

    // Fill background color
    g.setColor(bgColor);
    g.fillRect(r.x + 2, r.y + 2, r.width - 4, r.height - 4);
    g.setColor(oldColor);

    long tMin = Long.MAX_VALUE;
    long tMax = Long.MIN_VALUE;
    long vMin = Long.MAX_VALUE;
    long vMax = 1;

    int w = getWidth() - rightMargin - leftMargin - 10;
    int h = getHeight() - topMargin - bottomMargin;

    if (times.size > 1) {
      tMin = Math.min(tMin, times.time(0));
      tMax = Math.max(tMax, times.time(times.size - 1));
    }
    long viewRangeMS;
    if (viewRange > 0) {
      viewRangeMS = viewRange * MINUTE;
    } else {
      // Display full time range, but no less than a minute
      viewRangeMS = Math.max(tMax - tMin, 1 * MINUTE);
    }

    // Calculate min/max values
    for (Sequence seq : seqs) {
      if (seq.size > 0) {
        for (int i = 0; i < seq.size; i++) {
          if (seq.size == 1 || times.time(i) >= tMax - viewRangeMS) {
            long val = seq.value(i);
            if (val > Long.MIN_VALUE) {
              vMax = Math.max(vMax, val);
              vMin = Math.min(vMin, val);
            }
          }
        }
      } else {
        vMin = 0L;
      }
      if (unit == Unit.BYTES || !seq.isPlotted) {
        // We'll scale only to the first (main) value set.
        // TODO: Use a separate property for this.
        break;
      }
    }

    // Normalize scale
    vMax = normalizeMax(vMax);
    if (vMin > 0) {
      if (vMax / vMin > 4) {
        vMin = 0;
      } else {
        vMin = normalizeMin(vMin);
      }
    }

    g.setColor(fg);

    // Axes
    // Draw vertical axis
    int x = leftMargin - 18;
    int y = topMargin;
    FontMetrics fm = g.getFontMetrics();

    g.drawLine(x, y, x, y + h);

    int n = 5;
    if (("" + vMax).startsWith("2")) {
      n = 4;
    } else if (("" + vMax).startsWith("3")) {
      n = 6;
    } else if (("" + vMax).startsWith("4")) {
      n = 4;
    } else if (("" + vMax).startsWith("6")) {
      n = 6;
    } else if (("" + vMax).startsWith("7")) {
      n = 7;
    } else if (("" + vMax).startsWith("8")) {
      n = 8;
    } else if (("" + vMax).startsWith("9")) {
      n = 3;
    }

    // Ticks
    ArrayList<Long> tickValues = new ArrayList<Long>();
    tickValues.add(vMin);
    for (int i = 0; i < n; i++) {
      long v = i * vMax / n;
      if (v > vMin) {
        tickValues.add(v);
      }
    }
    tickValues.add(vMax);
    n = tickValues.size();

    String[] tickStrings = new String[n];
    for (int i = 0; i < n; i++) {
      long v = tickValues.get(i);
      tickStrings[i] = getSizeString(v, vMax);
    }

    // Trim trailing decimal zeroes.
    if (decimals > 0) {
      boolean trimLast = true;
      boolean removedDecimalPoint = false;
      do {
        for (String str : tickStrings) {
          if (!(str.endsWith("0") || str.endsWith("."))) {
            trimLast = false;
            break;
          }
        }
        if (trimLast) {
          if (tickStrings[0].endsWith(".")) {
            removedDecimalPoint = true;
          }
          for (int i = 0; i < n; i++) {
            String str = tickStrings[i];
            tickStrings[i] = str.substring(0, str.length() - 1);
          }
        }
      } while (trimLast && !removedDecimalPoint);
    }

    // Draw ticks
    int lastY = Integer.MAX_VALUE;
    for (int i = 0; i < n; i++) {
      long v = tickValues.get(i);
      y = topMargin + h - (int) (h * (v - vMin) / (vMax - vMin));
      g.drawLine(x - 2, y, x + 2, y);
      String s = tickStrings[i];
      if (unit == Unit.PERCENT) {
        s += "%";
      }
      int sx = x - 6 - fm.stringWidth(s);
      if (y < lastY - 13) {
        if (checkLeftMargin(sx)) {
          // Wait for next repaint
          return;
        }
        g.drawString(s, sx, y + 4);
      }
      // Draw horizontal grid line
      g.setColor(Color.lightGray);
      g.drawLine(r.x + 4, y, r.x + r.width - 4, y);
      g.setColor(fg);
      lastY = y;
    }

    // Draw horizontal axis
    x = leftMargin;
    y = topMargin + h + 15;
    g.drawLine(x, y, x + w, y);

    long t1 = tMax;
    if (t1 <= 0L) {
      // No data yet, so draw current time
      t1 = System.currentTimeMillis();
    }
    long tz = timeDF.getTimeZone().getOffset(t1);
    long tickInterval = calculateTickInterval(w, 40, viewRangeMS);
    if (tickInterval > 3 * HOUR) {
      tickInterval = calculateTickInterval(w, 80, viewRangeMS);
    }
    long t0 = tickInterval - (t1 - viewRangeMS + tz) % tickInterval;
    while (t0 < viewRangeMS) {
      x = leftMargin + (int) (w * t0 / viewRangeMS);
      g.drawLine(x, y - 2, x, y + 2);

      long t = t1 - viewRangeMS + t0;
      String str = formatClockTime(t);
      g.drawString(str, x, y + 16);
      // if (tickInterval > (1 * HOUR) && t % (1 * DAY) == 0) {
      if ((t + tz) % (1 * DAY) == 0) {
        str = formatDate(t);
        g.drawString(str, x, y + 27);
      }
      // Draw vertical grid line
      g.setColor(Color.lightGray);
      g.drawLine(x, topMargin, x, topMargin + h);
      g.setColor(fg);
      t0 += tickInterval;
    }

    // Plot values
    int start = 0;
    int nValues = 0;
    int nLists = seqs.size();
    if (nLists > 0) {
      nValues = seqs.get(0).size;
    }
    if (nValues == 0) {
      g.setColor(oldColor);
      return;
    } else {
      Sequence seq = seqs.get(0);
      // Find starting point
      for (int p = 0; p < seq.size; p++) {
        if (times.time(p) >= tMax - viewRangeMS) {
          start = p;
          break;
        }
      }
    }

    // Optimization: collapse plot of more than four values per pixel
    int pointsPerPixel = (nValues - start) / w;
    if (pointsPerPixel < 4) {
      pointsPerPixel = 1;
    }

    // Draw graphs
    // Loop backwards over sequences because the first needs to be painted on top
    for (int i = nLists - 1; i >= 0; i--) {
      int x0 = leftMargin;
      int y0 = topMargin + h + 1;

      Sequence seq = seqs.get(i);
      if (seq.isPlotted && seq.size > 0) {
        // Paint twice, with white and with color
        for (int pass = 0; pass < 2; pass++) {
          g.setColor((pass == 0) ? Color.white : seq.color);
          int x1 = -1;
          long v1 = -1;
          for (int p = start; p < nValues; p += pointsPerPixel) {
            // Make sure we get the last value
            if (pointsPerPixel > 1 && p >= nValues - pointsPerPixel) {
              p = nValues - 1;
            }
            int x2 = (int) (w * (times.time(p) - (t1 - viewRangeMS)) / viewRangeMS);
            long v2 = seq.value(p);
            if (v2 >= vMin && v2 <= vMax) {
              int y2 = (int) (h * (v2 - vMin) / (vMax - vMin));
              if (x1 >= 0 && v1 >= vMin && v1 <= vMax) {
                int y1 = (int) (h * (v1 - vMin) / (vMax - vMin));

                if (y1 == y2) {
                  // fillrect is much faster
                  g.fillRect(x0 + x1, y0 - y1 - pass, x2 - x1, 1);
                } else {
                  Graphics2D g2d = (Graphics2D) g;
                  Stroke oldStroke = null;
                  if (seq.transitionStroke != null) {
                    oldStroke = g2d.getStroke();
                    g2d.setStroke(seq.transitionStroke);
                  }
                  g.drawLine(x0 + x1, y0 - y1 - pass, x0 + x2, y0 - y2 - pass);
                  if (oldStroke != null) {
                    g2d.setStroke(oldStroke);
                  }
                }
              }
            }
            x1 = x2;
            v1 = v2;
          }
        }

        // Current value
        long v = seq.value(seq.size - 1);
        if (v >= vMin && v <= vMax) {
          if (bgIsLight) {
            g.setColor(seq.color);
          } else {
            g.setColor(fg);
          }
          x = r.x + r.width + 2;
          y = topMargin + h - (int) (h * (v - vMin) / (vMax - vMin));
          // a small triangle/arrow
          g.fillPolygon(new int[] {x + 2, x + 6, x + 6}, new int[] {y, y + 3, y - 3}, 3);
        }
        g.setColor(fg);
      }
    }

    int[] valueStringSlots = new int[nLists];
    for (int i = 0; i < nLists; i++) valueStringSlots[i] = -1;
    for (int i = 0; i < nLists; i++) {
      Sequence seq = seqs.get(i);
      if (seq.isPlotted && seq.size > 0) {
        // Draw current value

        // TODO: collapse values if pointsPerPixel >= 4

        long v = seq.value(seq.size - 1);
        if (v >= vMin && v <= vMax) {
          x = r.x + r.width + 2;
          y = topMargin + h - (int) (h * (v - vMin) / (vMax - vMin));
          int y2 = getValueStringSlot(valueStringSlots, y, 2 * 10, i);
          g.setFont(smallFont);
          if (bgIsLight) {
            g.setColor(seq.color);
          } else {
            g.setColor(fg);
          }
          String curValue = getFormattedValue(v, true);
          if (unit == Unit.PERCENT) {
            curValue += "%";
          }
          int valWidth = fm.stringWidth(curValue);
          String legend = (displayLegend ? seq.name : "");
          int legendWidth = fm.stringWidth(legend);
          if (checkRightMargin(valWidth) || checkRightMargin(legendWidth)) {
            // Wait for next repaint
            return;
          }
          g.drawString(legend, x + 17, Math.min(topMargin + h, y2 + 3 - 10));
          g.drawString(curValue, x + 17, Math.min(topMargin + h + 10, y2 + 3));

          // Maybe draw a short line to value
          if (y2 > y + 3) {
            g.drawLine(x + 9, y + 2, x + 14, y2);
          } else if (y2 < y - 3) {
            g.drawLine(x + 9, y - 2, x + 14, y2);
          }
        }
        g.setFont(oldFont);
        g.setColor(fg);
      }
    }
    g.setColor(oldColor);
  }