changeset 880:acbfd9752b11

New thread timeline review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-December/004934.html reviewed-by: rkennek
author Mario Torre <neugens.limasoftware@gmail.com>
date Thu, 20 Dec 2012 19:12:54 +0100
parents 59738c25578b
children 4902d8be1833
files client/swing/src/main/java/com/redhat/thermostat/client/swing/GraphicsUtils.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/ActionButtonTest.java thread/client-common/pom.xml thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadState.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadTimelineBean.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/Timeline.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/TimelineInfo.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/chart/ChartColors.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/model/timeline/Page.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/view/ThreadTimelineView.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTimelineController.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTimelineControllerTest.java thread/client-swing/pom.xml thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineChart.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineView.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/TimelineCellRenderer.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/TimelineComponent.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/TimelineRulerHeader.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/TimelineUtils.java
diffstat 19 files changed, 989 insertions(+), 632 deletions(-) [+]
line wrap: on
line diff
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/GraphicsUtils.java	Thu Dec 20 14:49:16 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/GraphicsUtils.java	Thu Dec 20 19:12:54 2012 +0100
@@ -92,4 +92,8 @@
         Paint paint = new GradientPaint(x, 0, start, 0, height, stop);
         g.setPaint(paint);
     }
+    
+    public Color deriveWithAlpha(Color color, int alpha) {
+        return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
+    }
 }
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/ActionButtonTest.java	Thu Dec 20 14:49:16 2012 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/ActionButtonTest.java	Thu Dec 20 19:12:54 2012 +0100
@@ -39,6 +39,9 @@
 import java.awt.Color;
 import java.awt.Component;
 import java.awt.Graphics;
+import java.util.Random;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
 
 import javax.swing.Icon;
 import javax.swing.JFrame;
@@ -51,6 +54,7 @@
 import org.fest.swing.fixture.FrameFixture;
 import org.fest.swing.fixture.JButtonFixture;
 import org.junit.After;
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -59,17 +63,26 @@
 public class ActionButtonTest {
 
     private FrameFixture frameFixture;
+    private Preferences prefs;
 
     @BeforeClass
     public static void setUpOnce() {
         FailOnThreadViolationRepaintManager.install();
     }
 
+    
+    @Before
+    public void setUp() {
+        Random random = new Random(); 
+        prefs = Preferences.userRoot().node(HeaderPanelTest.class.getName() + "." + random.nextInt());
+    }
+    
     @After
-    public void tearDown() {
+    public void tearDown() throws BackingStoreException {
         if (frameFixture != null) {
             frameFixture.cleanUp();
         }
+        prefs.removeNode();
     }
 
     @Test
@@ -81,7 +94,7 @@
                 JFrame frame = new JFrame();
                 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
-                HeaderPanel header = new HeaderPanel();
+                HeaderPanel header = new HeaderPanel(prefs, "wrong");
                 header.setHeader("Test");
 
                 Icon icon = new Icon() {
--- a/thread/client-common/pom.xml	Thu Dec 20 14:49:16 2012 +0100
+++ b/thread/client-common/pom.xml	Thu Dec 20 19:12:54 2012 +0100
@@ -111,6 +111,7 @@
             <Export-Package>
               com.redhat.thermostat.thread.client.common,
               com.redhat.thermostat.thread.client.common.view,
+              com.redhat.thermostat.thread.client.common.model.timeline,
               com.redhat.thermostat.thread.client.common.locale,
               com.redhat.thermostat.thread.client.common.chart,
               com.redhat.thermostat.thread.client.common.collector,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadState.java	Thu Dec 20 19:12:54 2012 +0100
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.common;
+
+public enum ThreadState {
+    NEW,
+    RUNNABLE,
+    BLOCKED,
+    WAITING,
+    TIMED_WAITING,
+    TERMINATED,
+    UNKNOWN,
+}
--- a/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadTimelineBean.java	Thu Dec 20 14:49:16 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.thread.client.common;
-
-import java.util.Date;
-
-public class ThreadTimelineBean implements Cloneable {
-
-    private long startTime;
-    private long stopTime;
-    private String name;
-    private Thread.State state;
-    
-    private boolean highlight;
-    
-    public boolean isHighlight() {
-        return highlight;
-    }
-    
-    public void setHighlight(boolean highlight) {
-        this.highlight = highlight;
-    }
-    
-    public Thread.State getState() {
-        return state;
-    }
-    
-    public void setState(Thread.State state) {
-        this.state = state;
-    }
-    
-    public String getName() {
-        return name;
-    }
-    
-    public void setName(String name) {
-        this.name = name;
-    }
-    
-    public long getStartTime() {
-        return startTime;
-    }
-    
-    public void setStartTime(long startTime) {
-        this.startTime = startTime;
-    }
-    
-    public long getStopTime() {
-        return stopTime;
-    }
-    
-    public void setStopTime(long stopTime) {
-        this.stopTime = stopTime;
-    }
-
-    @Override
-    public String toString() {
-        return "ThreadTimelineBean [name=" + name + ", state=" + state
-                + ", startTime=" + startTime + " (" + new Date(startTime) + ")"
-                + ", stopTime=" + stopTime + " (" + new Date(stopTime) + ")]";
-    }
-    
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((name == null) ? 0 : name.hashCode());
-        result = prime * result + (int) (startTime ^ (startTime >>> 32));
-        result = prime * result + ((state == null) ? 0 : state.hashCode());
-        result = prime * result + (int) (stopTime ^ (stopTime >>> 32));
-        return result;
-    }
-    
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-        if (obj == null)
-            return false;
-        if (getClass() != obj.getClass())
-            return false;
-        ThreadTimelineBean other = (ThreadTimelineBean) obj;
-        if (name == null) {
-            if (other.name != null)
-                return false;
-        } else if (!name.equals(other.name))
-            return false;
-        if (startTime != other.startTime)
-            return false;
-        if (state != other.state)
-            return false;
-        if (stopTime != other.stopTime)
-            return false;
-        return true;
-    }
-    
-    /**
-     * NOTE: A {@link ThreadTimelineBean} is contains another if they are
-     * either equals, or the the name, state and start time are the same
-     * (in other words, this method does not check the stop time, and is not a
-     * strict Set operation).
-     */
-    public boolean contains(ThreadTimelineBean other) {
-        if (equals(other)) {
-            return true;
-        }
-        if (getName().equals(other.getName())      &&
-            getState().equals(other.getState())    &&
-            getStartTime() == other.getStartTime())
-        {
-            return true;
-        }
-        
-        return false;
-    }
-    
-    @Override
-    public ThreadTimelineBean clone() {
-        ThreadTimelineBean copy = new ThreadTimelineBean();
-        copy.name = this.name;
-        copy.startTime = this.startTime;
-        copy.stopTime = this.stopTime;
-        copy.state = this.state;
-        return copy;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/Timeline.java	Thu Dec 20 19:12:54 2012 +0100
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.common;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+public class Timeline implements Iterable<TimelineInfo> {
+
+    private Deque<TimelineInfo> infos;
+    
+    private String name;
+    private long id;
+    
+    public Timeline(String name, long id) {
+        this.name = name;
+        this.id = id;
+        infos = new ArrayDeque<>();
+    }
+
+    public String getName() {
+        return name;
+    }
+    
+    public long getId() {
+        return id;
+    }
+    
+    @Override
+    public Iterator<TimelineInfo> iterator() {
+        return infos.descendingIterator();
+    }
+    
+    public TimelineInfo[] toArray() {
+        TimelineInfo[] result = new TimelineInfo[size()];
+        int i = 0;
+        for (TimelineInfo info : this) {
+            result[i++] = info;
+        }
+        return result;
+    }
+    
+    public void add(TimelineInfo info) {
+        infos.add(info);
+    }
+    
+    public int size() {
+        return infos.size();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/TimelineInfo.java	Thu Dec 20 19:12:54 2012 +0100
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.common;
+
+import com.redhat.thermostat.client.ui.Palette;
+import com.redhat.thermostat.storage.model.TimeStampedPojo;
+
+public class TimelineInfo implements TimeStampedPojo {
+
+    private Palette colour;
+    private long timeStamp;
+    
+    public TimelineInfo() {
+        timeStamp = 0;
+        colour = Palette.BLACK; 
+    }
+    
+    public TimelineInfo(Palette colour, long timeStamp) {
+        this.timeStamp = timeStamp;
+        this.colour = colour;
+    }
+    
+    public Palette getColor() {
+        return colour;
+    }
+    
+    public void setColor(Palette colour) {
+        this.colour = colour;
+    }
+    
+    public void setTimeStamp(long timestamp) {
+        this.timeStamp = timestamp;
+    }
+
+    @Override
+    public long getTimeStamp() {
+        return timeStamp;
+    }
+
+    @Override
+    public String toString() {
+        return "TimelineInfo [colour=" + colour + ", timeStamp=" + timeStamp + "]";
+    }
+}
--- a/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/chart/ChartColors.java	Thu Dec 20 14:49:16 2012 +0100
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/chart/ChartColors.java	Thu Dec 20 19:12:54 2012 +0100
@@ -46,38 +46,42 @@
         return getColor(Thread.State.valueOf(state));
     }
     
-    public static Color getColor(Thread.State state) {
-        Color result = null;
+    public static Palette getPaletteColor(Thread.State state) {
+        Palette result = null;
         
         switch (state) {
         case TIMED_WAITING:
-            result = Palette.PALE_RED.getColor();
+            result = Palette.PALE_RED;
             break;
             
         case NEW:
-            result = Palette.POMP_AND_POWER_VIOLET.getColor();
+            result = Palette.POMP_AND_POWER_VIOLET;
             break;
 
         case RUNNABLE:
-            result = Palette.PRUSSIAN_BLUE.getColor();
+            result = Palette.PRUSSIAN_BLUE;
             break;
 
         case TERMINATED:
-            result = Palette.GRAY.getColor();
+            result = Palette.GRAY;
             break;
 
         case BLOCKED:
-            result = Palette.RED.getColor();            
+            result = Palette.RED;            
             break;
 
         case WAITING:
-            result = Palette.GRANITA_ORANGE.getColor();            
+            result = Palette.GRANITA_ORANGE;            
             break;
 
         default:
-            result = Color.BLACK;            
+            result = Palette.BLACK;            
             break;
         }
         return result;
     }
+    
+    public static Color getColor(Thread.State state) {
+        return getPaletteColor(state).getColor();
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/model/timeline/Page.java	Thu Dec 20 19:12:54 2012 +0100
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.common.model.timeline;
+
+import com.redhat.thermostat.common.model.LongRange;
+
+public class Page {
+
+    private LongRange range;
+    public Page(LongRange range) {
+        this.range = range;
+    }
+    
+    public LongRange getSpan() {
+        return range;
+    }
+}
--- a/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/view/ThreadTimelineView.java	Thu Dec 20 14:49:16 2012 +0100
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/view/ThreadTimelineView.java	Thu Dec 20 19:12:54 2012 +0100
@@ -37,13 +37,12 @@
 package com.redhat.thermostat.thread.client.common.view;
 
 import java.util.List;
-import java.util.Map;
 
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
-import com.redhat.thermostat.thread.client.common.ThreadTimelineBean;
-import com.redhat.thermostat.thread.model.ThreadInfoData;
+import com.redhat.thermostat.common.model.LongRange;
+import com.redhat.thermostat.thread.client.common.Timeline;
 
 public abstract class ThreadTimelineView extends BasicView {
 
@@ -64,7 +63,5 @@
         threadTimelineNotifier.removeActionListener(listener);
     }
     
-    public abstract void displayStats(Map<ThreadInfoData, List<ThreadTimelineBean>> timelines, long start, long stop);
-    public abstract void setMarkersMessage(String left, String right);
-    public abstract void resetMarkerMessage();
+    public abstract void displayStats(List<Timeline> timelines, LongRange range);
 }
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTimelineController.java	Thu Dec 20 14:49:16 2012 +0100
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTimelineController.java	Thu Dec 20 19:12:54 2012 +0100
@@ -36,16 +36,21 @@
 
 package com.redhat.thermostat.thread.client.controller.impl;
 
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
 
+import com.redhat.thermostat.client.ui.Palette;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.Timer;
-import com.redhat.thermostat.thread.client.common.ThreadTimelineBean;
+import com.redhat.thermostat.common.model.LongRange;
+import com.redhat.thermostat.thread.client.common.Timeline;
+import com.redhat.thermostat.thread.client.common.TimelineInfo;
+import com.redhat.thermostat.thread.client.common.chart.ChartColors;
 import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
 import com.redhat.thermostat.thread.client.common.view.ThreadTimelineView;
 import com.redhat.thermostat.thread.client.common.view.ThreadTimelineView.ThreadTimelineViewAction;
@@ -57,7 +62,6 @@
     private ThreadCollector collector;
     
     private final String lock = new String("ThreadTimelineController"); 
-    private ThreadTimelineBean latestSelected;
     
     public ThreadTimelineController(ThreadTimelineView view, ThreadCollector collector, Timer timer) {
         super(timer, view);
@@ -71,96 +75,40 @@
         
         @Override
         public void actionPerformed(ActionEvent<ThreadTimelineViewAction> actionEvent) {
-            synchronized (lock) {
-                latestSelected = (ThreadTimelineBean) actionEvent.getPayload();
-            }
+            // TODO
         }
     }
     
     private class ThreadTimelineControllerAction implements Runnable {
         @Override
         public void run() {
-            ThreadTimelineBean _latestSelected = null;
+            
             synchronized (lock) {
-                if (latestSelected != null) {
-                    _latestSelected = latestSelected.clone();
-                }
-            }
-            
-            List<ThreadInfoData> infos = collector.getThreadInfo();
-            if(infos.size() > 0) {
-                
-                Map<ThreadInfoData, List<ThreadTimelineBean>> timelines = new HashMap<>();
-                
-                Map<ThreadInfoData, List<ThreadInfoData>> stats =
-                        ThreadInfoHelper.getThreadInfoDataMap(infos);
-                for (List<ThreadInfoData> beanList : stats.values()) {
-                    
-                    // the list is ordered in most recent first
-                    // the first element is the latest sample we have of this
-                    // thread, so we use it as stop time. 
-                    
-                    ThreadInfoData lastThreadInfo = beanList.get(beanList.size() - 1);
-                    Thread.State lastState = lastThreadInfo.getState();
-                    
-                    ThreadTimelineBean timeline = new ThreadTimelineBean();
-                    timeline.setName(lastThreadInfo.getThreadName());
-                    timeline.setState(lastThreadInfo.getState());
-                    timeline.setStartTime(lastThreadInfo.getTimeStamp()); 
-                    
-                    long stopTime = beanList.get(0).getTimeStamp();
-                    timeline.setStopTime(stopTime);
-                    
-                    Stack<ThreadTimelineBean> threadTimelines = new Stack<ThreadTimelineBean>();
-                    timelines.put(lastThreadInfo, threadTimelines);
-                    
-                    if (_latestSelected != null && _latestSelected.contains(timeline)) {
-                        timeline.setHighlight(true);
-                    }                    
-                    threadTimelines.push(timeline);
-                    
-                    for (int i = beanList.size() - 1; i >= 0; i--) {
-                        ThreadInfoData threadInfo = beanList.get(i);
-                        
-                        Thread.State currentState = threadInfo.getState();
-                        if (currentState != lastState) {
-                            lastState = currentState;
-                            
-                            timeline = threadTimelines.pop();
-                            timeline.setStopTime(threadInfo.getTimeStamp());
-                            
-                            if (_latestSelected != null && _latestSelected.contains(timeline)) {
-                                timeline.setHighlight(true);
+                // FIXME: only load latest, not all the info all the time
+                LongRange range = new LongRange(Long.MAX_VALUE, Long.MIN_VALUE);
+                List<ThreadInfoData> infos = collector.getThreadInfo();
+                if(infos.size() > 0) {
+                    Map<ThreadInfoData, List<ThreadInfoData>> stats = ThreadInfoHelper.getThreadInfoDataMap(infos);
+                    List<Timeline> timelines =  new ArrayList<>();
+                    for (List<ThreadInfoData> beanList : stats.values()) {
+                        Timeline timeline = new Timeline(beanList.get(0).getThreadName(), beanList.get(0).getThreadId());
+ 
+                        for (ThreadInfoData data : beanList) {
+                            Palette palette = ChartColors.getPaletteColor(data.getState());
+                            long timestamp = data.getTimeStamp();
+                            TimelineInfo info = new TimelineInfo(palette, timestamp);
+                            timeline.add(info);
+
+                            if (range.getMin() > timestamp) {
+                                range.setMin(timestamp);
                             }
-                            
-                            threadTimelines.push(timeline);
-                            
-                            timeline = new ThreadTimelineBean();
-                            timeline.setName(threadInfo.getThreadName());
-                            timeline.setState(threadInfo.getState());
-                            timeline.setStartTime(threadInfo.getTimeStamp());
-                            timeline.setStopTime(stopTime);
-
-                            lastThreadInfo = threadInfo;
-                            lastState = currentState;
-                            
-                            if (_latestSelected != null && _latestSelected.contains(timeline)) {
-                                timeline.setHighlight(true);
+                            if (range.getMax() < timestamp) {
+                                range.setMax(timestamp);
                             }
-
-                            // add the new thread stat
-                            threadTimelines.push(timeline);
                         }
+                        timelines.add(timeline);
                     }
-                }
-                
-                view.displayStats(timelines, infos.get(infos.size() - 1).getTimeStamp(), infos.get(0).getTimeStamp());
-
-                if (_latestSelected != null) {
-                    view.setMarkersMessage(new Date(_latestSelected.getStartTime()).toString(),
-                                           new Date(_latestSelected.getStopTime()).toString());
-                } else {
-                    view.resetMarkerMessage();
+                    view.displayStats(timelines, range);
                 }
             }
         }
--- a/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTimelineControllerTest.java	Thu Dec 20 14:49:16 2012 +0100
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTimelineControllerTest.java	Thu Dec 20 19:12:54 2012 +0100
@@ -55,7 +55,11 @@
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.Timer;
-import com.redhat.thermostat.thread.client.common.ThreadTimelineBean;
+import com.redhat.thermostat.common.model.LongRange;
+import com.redhat.thermostat.thread.client.common.Timeline;
+import com.redhat.thermostat.thread.client.common.TimelineInfo;
+import com.redhat.thermostat.thread.client.common.chart.ChartColors;
+//import com.redhat.thermostat.thread.client.common.ThreadTimelineBean;
 import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
 import com.redhat.thermostat.thread.client.common.view.ThreadTableView;
 import com.redhat.thermostat.thread.client.common.view.ThreadTimelineView;
@@ -140,32 +144,36 @@
         Runnable action = timerCaptor.getValue();
         action.run();
         
-        ArgumentCaptor<Map> mapCaptor = ArgumentCaptor.forClass(Map.class);
-        
-        verify(view).displayStats(mapCaptor.capture(), anyLong(), anyLong());
-        
-        Map viewResult = mapCaptor.getValue();
-        assertEquals(2, viewResult.size());
-        
-        List<ThreadTimelineBean> beanList = (List<ThreadTimelineBean>) viewResult.get(data1);
-        
-        assertEquals(2, beanList.size());
+        ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class);
+        ArgumentCaptor<LongRange> rangeCaptor = ArgumentCaptor.forClass(LongRange.class);
+
+        verify(view).displayStats(listCaptor.capture(), rangeCaptor.capture());
         
-        assertEquals("test1", beanList.get(0).getName());
-        assertEquals("test1", beanList.get(1).getName());
+        List viewResult = listCaptor.getValue();
+        LongRange rangeResult = rangeCaptor.getValue();
+        assertEquals(2, viewResult.size());
+        assertEquals(100, rangeResult.getMin());
+        assertEquals(3000, rangeResult.getMax());
 
-        beanList = (List<ThreadTimelineBean>) viewResult.get(data2);
-        assertEquals(2, beanList.size());
+        Timeline timeline = (Timeline) viewResult.get(0);
+        assertEquals(2, timeline.getId());
+        assertEquals("test2", timeline.getName());
+        assertEquals(3, timeline.size());
         
-        assertEquals("test2", beanList.get(0).getName());
-        assertEquals("test2", beanList.get(1).getName());
+        TimelineInfo [] timelineInfos = timeline.toArray();
+        assertEquals(ChartColors.getPaletteColor(data2.getState()), timelineInfos[0].getColor());
+        assertEquals(ChartColors.getPaletteColor(data4.getState()), timelineInfos[1].getColor());
+        assertEquals(ChartColors.getPaletteColor(data5.getState()), timelineInfos[2].getColor());
 
-        assertEquals(1000, beanList.get(0).getStartTime());
-        assertEquals(3000, beanList.get(0).getStopTime());
-        assertEquals(Thread.State.BLOCKED, beanList.get(0).getState());
-
-        assertEquals(3000, beanList.get(1).getStartTime());
-        assertEquals(Thread.State.BLOCKED, beanList.get(0).getState());        
+        
+        timeline = (Timeline) viewResult.get(1);
+        assertEquals(1, timeline.getId());
+        assertEquals("test1", timeline.getName());
+        assertEquals(2, timeline.size());
+        
+        timelineInfos = timeline.toArray();
+        assertEquals(ChartColors.getPaletteColor(data1.getState()), timelineInfos[0].getColor());
+        assertEquals(ChartColors.getPaletteColor(data3.getState()), timelineInfos[1].getColor());        
     }
 
     @Test
--- a/thread/client-swing/pom.xml	Thu Dec 20 14:49:16 2012 +0100
+++ b/thread/client-swing/pom.xml	Thu Dec 20 19:12:54 2012 +0100
@@ -115,6 +115,7 @@
             <Private-Package>
               com.redhat.thermostat.thread.client.swing.osgi,
               com.redhat.thermostat.thread.client.swing.impl,
+              com.redhat.thermostat.thread.client.swing.impl.timeline,
             </Private-Package>
             <!-- Do not autogenerate uses clauses in Manifests -->
             <_nouses>true</_nouses>
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineChart.java	Thu Dec 20 14:49:16 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,256 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.thread.client.swing.impl;
-
-import java.awt.BasicStroke;
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.GradientPaint;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Paint;
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.Shape;
-import java.awt.Stroke;
-import java.awt.geom.Rectangle2D;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import javax.swing.JFrame;
-import javax.swing.JPanel;
-import javax.swing.SwingUtilities;
-
-import com.redhat.thermostat.client.swing.GraphicsUtils;
-import com.redhat.thermostat.client.swing.components.GradientRoundBorder;
-import com.redhat.thermostat.client.ui.Palette;
-import com.redhat.thermostat.common.model.LongRange;
-import com.redhat.thermostat.common.model.LongRangeNormalizer;
-import com.redhat.thermostat.thread.client.common.ThreadTimelineBean;
-import com.redhat.thermostat.thread.client.common.chart.ChartColors;
-
-@SuppressWarnings("serial")
-public class SwingThreadTimelineChart extends JPanel {
-
-    private String leftMarkerMessage; 
-    private String rightMarkerMessage;
-    
-    public static final String HIGHLIGHT_THREAD_STATE_PROPERTY = "highlightThreadThreadProperty";
-    private ThreadTimelineBean selectedThread;
-    
-    private List<ThreadTimelineBean> timeline;
-    
-    private LongRangeNormalizer normalizer;
-
-    private Point clickArea;
-    
-    public SwingThreadTimelineChart(List<ThreadTimelineBean> timeline, long rangeStart, long rangeStop) {
-        this.timeline = timeline;
-    
-        LongRange range = new LongRange();
-        range.setMin(rangeStart);
-        range.setMax(rangeStop);
-        
-        setBorder(new GradientRoundBorder());
-        normalizer = new LongRangeNormalizer(range);
-    }
-
-    public void clickAndHighlightArea(Point point) {
-        clickArea = point;
-        repaint();
-    }
-   
-    public void unsetHighlightArea() {
-        clickArea = null;
-        selectedThread = null;
-        repaint();
-    }
-    
-    @Override
-    protected void paintComponent(Graphics g) {
-        
-        ThreadTimelineBean oldSelectedThread = selectedThread;
-        
-        normalizer.setMinNormalized(0);
-        normalizer.setMaxNormalized(getWidth() - 3);
-        
-        GraphicsUtils utils = GraphicsUtils.getInstance();
-        
-        Graphics2D graphics = utils.createAAGraphics(g);
-        
-        Font font = graphics.getFont();
-        graphics.setFont(font.deriveFont(font.getSize() - 2));
-        
-        int y = getHeight()/3;
-        graphics.clearRect(0, 0, getWidth(), getHeight());
-        graphics.drawString(timeline.get(0).getName(), 2, y);
-        
-        y = getHeight()/2;
-
-        graphics.translate(2, y);
-        if (clickArea != null) {
-            clickArea.translate(0, -y);
-        }
-        
-        int w = getWidth() - 4;
-        int h = 15;
-        
-        Shape shape = utils.getRoundShape(w, h);
-
-        Paint paint = new GradientPaint(0, 0, Palette.DARK_GRAY.getColor(), 0, h, Palette.WHITE.getColor());
-        graphics.setPaint(paint);        
-        graphics.fill(shape);        
-
-                
-        for (ThreadTimelineBean thread : timeline) {
-            
-            boolean isSelected = false;
-            normalizer.setValue(thread.getStartTime());
-            int x0 = (int) normalizer.getValueNormalized();
-
-            normalizer.setValue(thread.getStopTime());
-            int x1 = (int) normalizer.getValueNormalized();
-
-            Color currentThreadColour = ChartColors.getColor(thread.getState());
-            Rectangle currentArea = new Rectangle(x0, 0, x1 - x0, h);
-            if (clickArea != null && currentArea.contains(clickArea)) {
-                currentThreadColour = Palette.THERMOSTAT_BLU.getColor();
-                selectedThread = thread;
-                isSelected = true;
-                
-            } else if (thread.isHighlight()) {
-                currentThreadColour = Palette.THERMOSTAT_BLU.getColor();
-                isSelected = true;
-            }
-            
-            graphics.setColor(currentThreadColour);
-            graphics.fillRect(x0, 1, x1 - x0, h - 1);
-            
-            if (isSelected) {
-                paintMarks(graphics, x0, x1, h, thread.getStartTime(), thread.getStopTime(), ChartColors.getColor(thread.getState()));
-                String tooltipString = "";
-                if (leftMarkerMessage != null) {
-                    tooltipString = leftMarkerMessage + " - ";
-                }
-                if (rightMarkerMessage != null) {
-                    tooltipString += " " + rightMarkerMessage;
-                }
-                setToolTipText(tooltipString);
-            }
-        }
-        
-        graphics.setColor(ChartColors.getColor(timeline.get(timeline.size() - 1).getState()));
-        graphics.draw(shape);
-
-        graphics.dispose();
-        
-        if (selectedThread != null || oldSelectedThread != null) {
-            firePropertyChange(HIGHLIGHT_THREAD_STATE_PROPERTY, oldSelectedThread, selectedThread);
-        }
-    }
-    
-    private void paintMarks(Graphics2D graphics, int x0, int x1, int y, long start, long stop, Color colour) {
-        
-        graphics.setColor(colour);
-        graphics.fillRect(x0, y + 5, x1 - x0, 2);
-        
-        GraphicsUtils utils = GraphicsUtils.getInstance();
-        FontMetrics metrics = utils.getFontMetrics(this, graphics.getFont());
-        
-        graphics.setColor(getForeground());
-        if (leftMarkerMessage != null) {
-            Rectangle2D rect = metrics.getStringBounds(leftMarkerMessage, graphics);
-            graphics.drawString(leftMarkerMessage, x0 - (int) rect.getWidth(), y - (int) rect.getY());
-        }
-        
-        if (rightMarkerMessage != null) {
-            Rectangle2D rect = metrics.getStringBounds(leftMarkerMessage, graphics);
-            graphics.drawString(rightMarkerMessage, x1, y - (int) rect.getY());
-        }
-    }
-    
-    public static void main(String[] args) {
-        SwingUtilities.invokeLater(new Runnable() {
-            
-            @Override
-            public void run() {
-                JFrame frame = new JFrame();
-                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-                
-                ThreadTimelineBean bean1 = new ThreadTimelineBean();
-                bean1.setName("test");
-                bean1.setStartTime(0);
-                bean1.setStopTime(1000);
-                bean1.setState(Thread.State.BLOCKED);
-                
-                ThreadTimelineBean bean2 = new ThreadTimelineBean();
-                bean2.setName("test");
-                bean2.setStartTime(1000);
-                bean2.setStopTime(2000);
-                bean2.setState(Thread.State.RUNNABLE);
-                
-                ThreadTimelineBean bean3 = new ThreadTimelineBean();
-                bean3.setName("test");
-                bean3.setStartTime(2000);
-                bean3.setStopTime(2100);
-                bean3.setState(Thread.State.TIMED_WAITING);
-                
-                List<ThreadTimelineBean> timeline = new ArrayList<>();
-                timeline.add(bean1);
-                timeline.add(bean2);
-                timeline.add(bean3);
-
-                bean3.setHighlight(true);
-                
-                SwingThreadTimelineChart chart = new SwingThreadTimelineChart(timeline, 0, 2500);
-                chart.setMarkersMessage(new Date(bean3.getStartTime()).toString(), new Date(bean3.getStopTime()).toString());
-                
-                frame.add(chart);
-                frame.setSize(800, 150);
-                
-                frame.setVisible(true);
-            }
-        });
-    }
-
-    public void setMarkersMessage(String leftMarkerMessage, String rightMarkerMessage) {
-        this.leftMarkerMessage = leftMarkerMessage;
-        this.rightMarkerMessage = rightMarkerMessage;   
-    }
-}
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineView.java	Thu Dec 20 14:49:16 2012 +0100
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineView.java	Thu Dec 20 19:12:54 2012 +0100
@@ -55,23 +55,32 @@
 import javax.swing.ListCellRenderer;
 import javax.swing.SwingUtilities;
 import javax.swing.SwingWorker;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 
 import com.redhat.thermostat.client.swing.SwingComponent;
 import com.redhat.thermostat.client.ui.ComponentVisibleListener;
-import com.redhat.thermostat.thread.client.common.ThreadTimelineBean;
+import com.redhat.thermostat.common.model.LongRange;
+
+import com.redhat.thermostat.thread.client.common.Timeline;
+import com.redhat.thermostat.thread.client.common.TimelineInfo;
 import com.redhat.thermostat.thread.client.common.view.ThreadTimelineView;
+import com.redhat.thermostat.thread.client.swing.impl.timeline.TimelineCellRenderer;
+import com.redhat.thermostat.thread.client.swing.impl.timeline.TimelineComponent;
+import com.redhat.thermostat.thread.client.swing.impl.timeline.TimelineRulerHeader;
+import com.redhat.thermostat.thread.client.swing.impl.timeline.TimelineUtils;
 import com.redhat.thermostat.thread.model.ThreadInfoData;
 
-public class SwingThreadTimelineView extends ThreadTimelineView  implements SwingComponent  {
+public class SwingThreadTimelineView extends ThreadTimelineView implements SwingComponent  {
 
     private final String lock = new String("SwingThreadTimelineViewLock");
         
     private JPanel timeLinePanel;
-    private JList<SwingThreadTimelineChart> chartList;
-    private DefaultListModel<SwingThreadTimelineChart> chartModel;
+    private JList<TimelineComponent> chartList;
+    private DefaultListModel<TimelineComponent> chartModel;
     
-    private String leftMarkerMessage; 
-    private String rightMarkerMessage;
+    private TimelineRulerHeader header;
+    private JScrollPane scrollPane;
     
     public SwingThreadTimelineView() {
         timeLinePanel = new JPanel();
@@ -89,117 +98,74 @@
         
         timeLinePanel.setLayout(new BorderLayout(0, 0));
         
-        
         chartModel = new DefaultListModel<>();
         chartList = new JList<>(chartModel);
         
-        chartList.setCellRenderer(new ChartRenderer());
-        chartList.addMouseListener(new ChartListListener());
+        chartList.setCellRenderer(new TimelineCellRenderer());
         
-        JScrollPane scrollPane = new JScrollPane(chartList);
-        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
-        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        JScrollPane scrollPane = createScrollPane();
         
         timeLinePanel.add(scrollPane, BorderLayout.CENTER);
         ThreadTimelineLegendPanel timelineLegend = new ThreadTimelineLegendPanel();
         timeLinePanel.add(timelineLegend, BorderLayout.SOUTH);
     }
     
-    @Override
-    public void displayStats(final Map<ThreadInfoData, List<ThreadTimelineBean>> timelines, final long start, final long stop) {
+    private JScrollPane createScrollPane() {
+        scrollPane = new JScrollPane(chartList);
+        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
 
+        long now = System.currentTimeMillis();
+        header = new TimelineRulerHeader(new LongRange(now, now + TimelineUtils.STEP), scrollPane);
+        scrollPane.setColumnHeaderView(header);
+        scrollPane.getHorizontalScrollBar().getModel().addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                scrollPane.repaint();
+            }
+        });
+        scrollPane.getVerticalScrollBar().getModel().addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                scrollPane.repaint();
+            }
+        });        
+        return scrollPane;
+    }
+    
+    @Override
+    public void displayStats(final List<Timeline> timelines, final LongRange range) {
         SwingUtilities.invokeLater(new Runnable() {
-            
             @Override
             public void run() {
-                String _leftMarkerMessage = null; 
-                String _rightMarkerMessage = null;
-                synchronized (lock) {
-                    _leftMarkerMessage = leftMarkerMessage;
-                    _rightMarkerMessage = rightMarkerMessage;                        
+                chartModel.removeAllElements();
+                for (Timeline timeline : timelines) {
+                    chartModel.addElement(new TimelineComponent(range, timeline, scrollPane));
                 }
-                
-                chartModel.clear();
-                for (List<ThreadTimelineBean> timeline : timelines.values()) {
-                    SwingThreadTimelineChart panel = new SwingThreadTimelineChart(timeline, start, stop);
-                    panel.setPreferredSize(new Dimension(chartList.getWidth(), 75));
-                    panel.setMarkersMessage(_leftMarkerMessage, _rightMarkerMessage);
-                    panel.addPropertyChangeListener(SwingThreadTimelineChart.HIGHLIGHT_THREAD_STATE_PROPERTY,
-                                                    new SelectedThreadListener());
-                    panel.setMarkersMessage(_leftMarkerMessage, _rightMarkerMessage);
-                    chartModel.addElement(panel);
-                }
+                header.getRange().setMin(range.getMin());
+                header.getRange().setMax(range.getMax());
             }
         });
     }
     
-    private class SelectedThreadListener implements PropertyChangeListener {
-        @Override
-        public void propertyChange(final PropertyChangeEvent evt) {
-            SwingWorker<Void, Void> notifier = new SwingWorker<Void, Void>() {
-                @Override
-                protected Void doInBackground() throws Exception {
-                    SwingThreadTimelineView.this.
-                    threadTimelineNotifier.fireAction(ThreadTimelineView.ThreadTimelineViewAction.THREAD_TIMELINE_SELECTED,
-                                                      evt.getNewValue());
-                    return null;
-                }
-            };
-            notifier.execute();
-        }
-    }
-    
-    private class ChartRenderer implements ListCellRenderer<SwingThreadTimelineChart> {
-        @Override
-        public Component getListCellRendererComponent(JList<? extends SwingThreadTimelineChart> list,
-                                                      SwingThreadTimelineChart chart,
-                                                      int index, boolean isSelected,
-                                                      boolean cellHasFocus)
-        {
-            if (!isSelected) {
-                chart.unsetHighlightArea();
-            }
-            return chart;
-        }
-    }
-    
-    private class ChartListListener extends MouseAdapter {
-        
-        @Override
-        public void mouseClicked(MouseEvent e) {
-            int index = chartList.getSelectedIndex();
-            if (index > 0) {
-                Point listIndex = chartList.indexToLocation(index);
-                Point absoluteLocation = e.getPoint();
-                listIndex.x = absoluteLocation.x;
-                if (index != 0) {
-                    listIndex.y = absoluteLocation.y - listIndex.y;
-                }
-                
-                SwingThreadTimelineChart chart = chartModel.get(index);
-                chart.clickAndHighlightArea(listIndex);                
-            }
-        }
-    }
+//    private class SelectedThreadListener implements PropertyChangeListener {
+//        @Override
+//        public void propertyChange(final PropertyChangeEvent evt) {
+//            SwingWorker<Void, Void> notifier = new SwingWorker<Void, Void>() {
+//                @Override
+//                protected Void doInBackground() throws Exception {
+//                    SwingThreadTimelineView.this.
+//                    threadTimelineNotifier.fireAction(ThreadTimelineView.ThreadTimelineViewAction.THREAD_TIMELINE_SELECTED,
+//                                                      evt.getNewValue());
+//                    return null;
+//                }
+//            };
+//            notifier.execute();
+//        }
+//    }
     
     @Override
     public Component getUiComponent() {
         return timeLinePanel;
     }
-    
-    @Override
-    public void resetMarkerMessage() {
-        synchronized (lock) {
-            this.leftMarkerMessage = null;
-            this.rightMarkerMessage = null;            
-        }
-    }
-    
-    @Override
-    public void setMarkersMessage(String left, String right) {
-        synchronized (lock) {
-            this.leftMarkerMessage = left;
-            this.rightMarkerMessage = right;            
-        }
-    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/TimelineCellRenderer.java	Thu Dec 20 19:12:54 2012 +0100
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.swing.impl.timeline;
+
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+
+@SuppressWarnings("serial")
+public class TimelineCellRenderer extends JPanel implements ListCellRenderer<TimelineComponent> {
+
+    @Override
+    public TimelineComponent getListCellRendererComponent(JList<? extends TimelineComponent> list,
+                                                          TimelineComponent value,
+                                                          int index, boolean isSelected,
+                                                          boolean cellHasFocus)
+    {
+        value.setSelected(isSelected);
+        return value;
+    } 
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/TimelineComponent.java	Thu Dec 20 19:12:54 2012 +0100
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.swing.impl.timeline;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.beans.Transient;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import com.redhat.thermostat.client.swing.GraphicsUtils;
+import com.redhat.thermostat.client.swing.components.GradientPanel;
+import com.redhat.thermostat.client.ui.Palette;
+import com.redhat.thermostat.common.model.LongRange;
+import com.redhat.thermostat.common.model.LongRangeNormalizer;
+import com.redhat.thermostat.thread.client.common.Timeline;
+import com.redhat.thermostat.thread.client.common.TimelineInfo;
+
+@SuppressWarnings("serial")
+public class TimelineComponent extends GradientPanel {
+    
+    private boolean selected = false;
+    
+    private Timeline timeline;
+    private JScrollPane scrollPane;
+    private LongRange range;
+    public TimelineComponent(LongRange range, Timeline timeline, JScrollPane scrollPane) {
+        super(Palette.LIGHT_GRAY.getColor(), Palette.WHITE.getColor());
+        this.range = range;
+        this.scrollPane = scrollPane;
+        this.timeline = timeline;
+    }
+
+    public void setSelected(boolean selected) {
+        this.selected = selected;
+    }
+    
+    @Override
+    protected void paintComponent(Graphics g) {
+        
+        Graphics2D graphics = GraphicsUtils.getInstance().createAAGraphics(g);
+        Rectangle bounds = g.getClipBounds();
+
+        if (!selected) {
+            super.paintComponent(g);
+        } else {
+            graphics.setColor(Palette.EGYPTIAN_BLUE.getColor());
+            graphics.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
+        }
+        
+        int height = getHeight();
+        int currentValue = scrollPane.getHorizontalScrollBar().getValue();
+        int totalInc = TimelineUtils.drawMarks(range, graphics, bounds, currentValue, getWidth(), height);
+        
+        drawBoldMarks(graphics, currentValue, bounds, totalInc);
+        Color lastColor = drawTimeline(graphics, currentValue, bounds);
+        
+        drawThreadName(graphics, bounds, lastColor);
+        
+        graphics.dispose();
+    }
+    
+    private void drawThreadName(Graphics2D graphics, Rectangle bounds, Color lastColor) {
+        GraphicsUtils utils = GraphicsUtils.getInstance();
+        
+        Color up = utils.deriveWithAlpha(Palette.WHITE.getColor(), 200);
+        Color bottom = utils.deriveWithAlpha(Palette.GRAY.getColor(), 200);
+        Paint gradient = new GradientPaint(0, 0, up, 0, getHeight(), bottom);
+        
+        Font font = TimelineUtils.FONT;
+        
+        graphics.setFont(font);
+        graphics.setPaint(gradient);
+        
+        String value = timeline.getName();
+        
+        int stringWidth = (int) font.getStringBounds(value, graphics.getFontRenderContext()).getWidth() - 1;
+        int stringHeight = (int) font.getStringBounds(value, graphics.getFontRenderContext()).getHeight();
+        graphics.fillRect(bounds.x + 1, bounds.y + 12, stringWidth + 4, stringHeight + 4);
+        
+        graphics.setColor(Palette.THERMOSTAT_BLU.getColor());                
+        graphics.drawString(value, bounds.x + 1, bounds.y + stringHeight + 12);
+        
+        graphics.setColor(lastColor);                
+        graphics.drawLine(bounds.x + 1, bounds.y + 12 + stringHeight + 4, bounds.x + stringWidth + 4, bounds.y + 12 + stringHeight + 4);
+    }
+    
+    private Color drawTimeline(Graphics2D graphics, int currentValue, Rectangle bounds) {
+        
+        if (timeline.size() == 0) {
+            return Palette.GRAY.getColor();
+        }
+        
+        TimelineInfo[] infos = timeline.toArray();
+        Color lastColor = infos[infos.length - 1].getColor().getColor();
+        
+        LongRangeNormalizer normalizer = new LongRangeNormalizer(range, 0, getWidth());
+        
+        for (int i = 0; i < infos.length - 1; i++) {
+            TimelineInfo info1 = infos[i];
+            TimelineInfo info2 = infos[i + 1];
+            
+            normalizer.setValue(info1.getTimeStamp());
+            int x0 = (int) normalizer.getValueNormalized();
+
+            normalizer.setValue(info2.getTimeStamp());
+            int x1 = (int) normalizer.getValueNormalized();
+            
+            graphics.setColor(info1.getColor().getColor());
+            graphics.fillRect(x0, 5, x1 - x0 + 1, 5);
+        }
+        
+        normalizer.setValue(infos[infos.length - 1].getTimeStamp());
+        int x0 = (int) normalizer.getValueNormalized();
+
+        normalizer.setValue(infos[infos.length - 1].getTimeStamp() + 250);
+        int x1 = (int) normalizer.getValueNormalized();
+
+        graphics.setColor(lastColor);        
+        graphics.fillRect(x0, 5, x1 - x0 + 1, 5);
+        
+        return lastColor;
+    }
+    
+    private void drawBoldMarks(Graphics2D graphics, int currentValue, Rectangle bounds, int totalInc) {
+
+        long round = range.getMin() % 10000;
+        int shift = (int) (round / TimelineUtils.STEP) * totalInc;
+        
+        int lowerBound = bounds.x - (4 * totalInc);
+        int x = ((bounds.x - currentValue) - shift);
+        
+        int increment = 0;
+        int height = getHeight();
+
+        graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
+        int upperBound = (bounds.x + bounds.width);
+        for (int i = x; i < upperBound; i += totalInc) {
+            if (increment % 10 == 0 && (i >= lowerBound)) {
+                graphics.drawLine(i, 0, i, height);
+            }
+            increment++;
+        }
+    }
+    
+    public LongRange getRange() {
+        return range;
+    }
+    
+    @Override
+    public int getHeight() {
+        return 40;
+    }
+    
+    @Override
+    public int getWidth() {
+        return TimelineUtils.calculateWidth(range);
+    }
+    
+    @Override
+    public Dimension getSize() {
+        return getPreferredSize();
+    }
+    
+    @Override
+    @Transient
+    public Dimension getPreferredSize() {
+        return new Dimension(getWidth(), getHeight());
+    }
+        
+    public static void main(String[] args) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                
+                LongRange range = new LongRange(50000, 2000000); // 31558464000L
+                Timeline timeline = new Timeline("Test", 1000);
+                timeline.add(new TimelineInfo(Palette.THERMOSTAT_BLU, range.getMax() - 1000));
+                timeline.add(new TimelineInfo(Palette.TUNDRA_GREEN, 152000));
+                timeline.add(new TimelineInfo(Palette.DIRTY_CYAN, 63000));
+                timeline.add(new TimelineInfo(Palette.THERMOSTAT_BLU, 62000));
+                timeline.add(new TimelineInfo(Palette.THERMOSTAT_RED, 60210));
+                timeline.add(new TimelineInfo(Palette.THERMOSTAT_BLU, 51299));
+
+                final JFrame frame = new JFrame();
+                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+                
+                
+                final JScrollPane scrollPane = new JScrollPane();
+                DefaultListModel<TimelineComponent> chartModel = new DefaultListModel<>();
+                for (int i = 0; i < 100; i++) {
+                    chartModel.addElement(new TimelineComponent(range, timeline, scrollPane));
+                }
+                
+                final JList<TimelineComponent> stuff = new JList<>(chartModel);
+                stuff.setCellRenderer(new TimelineCellRenderer());
+                
+                scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+                scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+                scrollPane.getHorizontalScrollBar().setUnitIncrement(TimelineUtils.INC);
+                
+                final TimelineRulerHeader header = new TimelineRulerHeader(range, scrollPane);
+                
+                scrollPane.setColumnHeaderView(header);
+                scrollPane.setViewportView(stuff);
+                scrollPane.getVerticalScrollBar().getModel().addChangeListener(new ChangeListener() {
+                    @Override
+                    public void stateChanged(ChangeEvent e) {
+                        scrollPane.repaint();
+                    }
+                });
+                
+                scrollPane.getHorizontalScrollBar().getModel().addChangeListener(new ChangeListener() {
+                    
+                    @Override
+                    public void stateChanged(ChangeEvent e) {
+                        scrollPane.repaint();
+                    }
+                });
+                
+                frame.add(scrollPane);
+                frame.setSize(300, 300);
+                frame.setVisible(true);
+            }
+        });
+    }     
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/TimelineRulerHeader.java	Thu Dec 20 19:12:54 2012 +0100
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.swing.impl.timeline;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.beans.Transient;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.JScrollPane;
+
+import com.redhat.thermostat.client.swing.GraphicsUtils;
+import com.redhat.thermostat.client.swing.components.GradientPanel;
+import com.redhat.thermostat.client.ui.Palette;
+import com.redhat.thermostat.common.model.LongRange;
+
+@SuppressWarnings("serial")
+public class TimelineRulerHeader extends GradientPanel {
+
+    private LongRange range;
+    private JScrollPane scrollPane;
+    
+    public TimelineRulerHeader(LongRange range, JScrollPane scrollPane) {
+        
+        super(Palette.LIGHT_GRAY.getColor(), Palette.WHITE.getColor());
+        
+        this.range = range;
+        this.scrollPane = scrollPane;
+    }
+    
+    public LongRange getRange() {
+        return range;
+    }
+    
+    @Override
+    public int getHeight() {
+        return 25;
+    }
+    
+    @Override
+    @Transient
+    public Dimension getPreferredSize() {
+        Dimension dim = super.getPreferredSize();
+        dim.height = getHeight();
+        return dim;
+    }
+    
+    @Override
+    public Dimension getSize() {
+        return getPreferredSize();
+    }
+    
+    @Override
+    protected void paintComponent(Graphics g) {
+        
+        super.paintComponent(g);
+
+        Graphics2D graphics = GraphicsUtils.getInstance().createAAGraphics(g);
+
+        int currentValue = scrollPane.getHorizontalScrollBar().getValue();
+
+        Rectangle bounds = g.getClipBounds();
+        int totalInc = TimelineUtils.drawMarks(range, graphics, bounds, currentValue,
+                                               TimelineUtils.calculateWidth(range),
+                                               getHeight(), true);
+        
+        drawTimelineStrings(graphics, currentValue, bounds, totalInc);
+        
+        graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
+        graphics.drawLine(bounds.x, bounds.height - 1, bounds.width, bounds.height - 1);
+        
+        graphics.dispose();
+    }
+    
+    private void drawTimelineStrings(Graphics2D graphics, int currentValue, Rectangle bounds, int totalInc) {
+        
+        Font font = TimelineUtils.FONT;
+        
+        graphics.setFont(font);
+        
+        DateFormat df = new SimpleDateFormat("HH:mm:ss");
+        
+        Paint gradient = new GradientPaint(0, 0, Palette.WHITE.getColor(), 0, getHeight(), Palette.GRAY.getColor());
+        
+        graphics.setColor(Palette.EARL_GRAY.getColor());
+        
+        long round = range.getMin() % (TimelineUtils.STEP * 10);
+        int shift = (int) (round / TimelineUtils.STEP) * totalInc;
+        long currentTime = range.getMin() - round;
+        
+        int lowerBound = bounds.x - (4 * totalInc);
+        int x = ((bounds.x - currentValue) - shift);
+        
+        long increment = 0;
+        int height = getHeight();
+        for (int i = x; i < bounds.width; i += totalInc) {
+            if (increment % 10 == 0 && i >= lowerBound) {
+                graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
+                graphics.drawLine(i, 0, i, height);
+                
+                graphics.setPaint(gradient);
+
+                String value = df.format(new Date(currentTime));
+
+                int stringWidth = (int) font.getStringBounds(value, graphics.getFontRenderContext()).getWidth() - 1;
+                int stringHeight = (int) font.getStringBounds(value, graphics.getFontRenderContext()).getHeight();
+                graphics.fillRect(i + 1, bounds.y + 5, stringWidth + 4, stringHeight + 4);
+                
+                graphics.setColor(Palette.THERMOSTAT_BLU.getColor());                
+                graphics.drawString(value, i + 1, bounds.y + stringHeight + 5);
+            }
+            currentTime += TimelineUtils.STEP;
+            increment++;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/TimelineUtils.java	Thu Dec 20 19:12:54 2012 +0100
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.swing.impl.timeline;
+
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+
+import com.redhat.thermostat.client.ui.Palette;
+import com.redhat.thermostat.common.model.LongRange;
+import com.redhat.thermostat.common.model.LongRangeNormalizer;
+
+
+public class TimelineUtils {
+    public static final int INC = 50;
+    public static final int STEP = 1000;
+    public static final Font FONT = new Font("SansSerif", Font.PLAIN, 10);
+
+    public static int calculateWidth(LongRange range) {
+        long span = range.getMax() - range.getMin();
+        int width = (int) (span / TimelineUtils.INC);
+        return width;
+    }
+    
+    public static int drawMarks(LongRange range, Graphics2D graphics, Rectangle bounds, int currentValue, int width, int height) {
+        return drawMarks(range, graphics, bounds, currentValue, width, height, false);
+    }
+    
+    public static int drawMarks(LongRange range, Graphics2D graphics, Rectangle bounds, int currentValue, int width, int height, boolean darkerTop) {
+        LongRangeNormalizer normalizer = new LongRangeNormalizer(range, 0, width);
+        normalizer.setValue(range.getMin() + TimelineUtils.STEP);
+        int totalInc = (int) normalizer.getValueNormalized();
+
+        int inc = currentValue % totalInc;
+        int x = (bounds.x - inc);
+        
+        graphics.setColor(Palette.GRAY.getColor());
+        int upperBound = (bounds.x + bounds.width);
+
+        for (int i = x; i < upperBound; i += totalInc) {
+            graphics.drawLine(i, 0, i, height);
+            if (darkerTop) {
+                graphics.setColor(Palette.DARK_GRAY.getColor());
+                graphics.drawLine(i, 0, i, 5);
+                graphics.setColor(Palette.GRAY.getColor());
+            }
+        }
+        
+        return totalInc;
+    }
+}