├── .gitignore
├── Instructions.md
├── README.md
├── package-lock.json
├── package.json
├── public
├── demo.gif
└── index.html
└── src
├── dateAxis.js
├── index.js
├── item.js
├── itemDurationDragControl.js
├── itemName.js
├── mouseMonitor.js
├── renderSizes.js
├── timeline.css
├── timeline.js
└── timelineItems.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/Instructions.md:
--------------------------------------------------------------------------------
1 | ## High level objective:
2 |
3 | Design and implement a component for visualizing events on a timeline.
4 |
5 | ## Details:
6 |
7 | Your timeline layout should arrange events in a compact space-efficient way: generally speaking, if event A ends before event B starts, the bars for those events can share the same horizontal lane, instead of existing on separate lanes. You may want to slightly relax this constraint to fit in the name of the event (for example, if the event's bar is too short, or the event's name is too long).
8 |
9 | The input to the component should be an array of events, where each event has a name, start date, and end date.
10 |
11 | The start and end dates will be formatted as YYYY-MM-DD date strings, for example: “2018-12-23”. You don't need to worry about hours, minutes, seconds, or time zones.
12 |
13 | You can assume every event's end date is the same or later than its start date.
14 |
15 | Avoid using libraries that solve too much of the problem. General purpose libraries like React are definitely okay, but a library that calculates the layout for a timeline is not, for example. This also applies to the CSS Grid `grid-auto-flow` property (but you may use CSS Grid for positioning).
16 |
17 | After you have a basic read-only timeline showing up, here are some potential improvements to attempt:
18 |
19 | * Allow zooming in and out of the timeline.
20 | * Allow dragging and dropping to change the start date and/or end date for an event.
21 | * Allow editing the name of events inline.
22 | * Any other polish or useful enhancements you can think of.
23 |
24 | Include a README that covers:
25 |
26 | * How long you spent on the assignment.
27 | * What you like about your implementation.
28 | * What you would change if you were going to do it again.
29 | * How you made your design decisions. For example, if you looked at other timelines for inspiration, please note that.
30 | * How you would test this if you had more time.
31 |
32 | If you did not use the starter code, please also include instructions on how to build and run your project so we can see and interact with the timeline component you built. It should render the sample data included in "src/timelineItems.js"
33 |
34 | What we're looking for:
35 |
36 | * Clean, readable, maintainable code.
37 | * A sensible user experience and design for the final product.
38 |
39 | ## Starter code:
40 |
41 | To use the starter code: navigate to this project directory, run `npm install` to install dependencies (this takes a couple minutes), and then run `npm start` to initialize and connect to a node server with your default browser. Please feel free to use as much or as little of the starter code as you'd like.
42 |
43 | ## Sample data:
44 |
45 | The "src/timelineItems.js" file has some sample data you can use to get started.
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Timeline
2 |
3 | Mary Rose Cook / mary@maryrosecook.com
4 |
5 | ## Build and run
6 |
7 | Navigate to this project directory, run `npm install` to install dependencies (this takes a couple minutes), and then run `npm start` to initialize and connect to a node server with your default browser.
8 |
9 | 
10 |
11 | ## What I like about my implementation
12 |
13 | ### The code
14 |
15 | * Each concern is separated into its own component. This makes the code more readable and maintainable.
16 |
17 | * The code for allocating timeline items to rows is succinct. (See `Timeline.itemsWithRow`.)
18 |
19 | * The code for allocating timeline items to rows is mostly decoupled from the rest of the code that edits, displays and annotates timeline items. It receives some items and returns a set of row assignments. It only needs a function to map between a date and horizontal placement, and a function to determine the width of an item. (See `Timeline.itemsWithRow`.)
20 |
21 | * The logic for translating between screen coordinates and dates is isolated to two functions: `Timeline.dateToColumn` and `Timeline.xPositionToDate`.
22 |
23 | ### The design
24 |
25 | * The rows reflow after the user edits a timeline date. But they don't jump around while the user is still in the middle of editing.
26 |
27 | ## What I'd change
28 |
29 | ### The code
30 |
31 | * I'd find a way to amalgamate the mouse state tracking code (`MouseMonitor`) with the date drag control code (`ItemDurationDragControl`). I couldn't figure out a nice way to get mouse clicks and moves without putting the mouse tracking code near the top level. This reduced coherence (because those chunks of code are distant from each other) and increased complexity (see that `ItemDurationDragControl.handleDrag` has to monitor incoming props rather that just respond to events).
32 |
33 | * I'd add PropTypes to make future refactors easier.
34 |
35 | * (My first implementation used some plain JS objects (`Timeline`, `Row`, `Item`) to calculate and update item positioning. The idea was to keep that complexity in one place. But, React components are supposed to be for layout. So the layout code ended up spread between the JS objects and the components. Things became a lot simpler when I removed the objects.)
36 |
37 | * I'd consider using a min heap when laying out the timeline items in rows. When finding a row in which to place an item, the current code iterates the rows and returns the first one that has space for the item. Instead of this iteration, I could use a heap and pop off the first row that has space in `O(log(n))` time. (It seems likely there are better performance improvements that making this change, so I'd check this was a performance bottleneck first.)
38 |
39 | ### The design
40 |
41 | * You can't set an item's start or end to a date outside the current date span of the timeline. To solve this, I'd make dragging a control to the edge of the timeline scroll the view.
42 |
43 | * The date row at the top is too cluttered. The next design I'd try would be like the one below. This would reduce the amount of text, and allow faster scanning of the span of time.
44 |
45 | ```
46 | 2018
47 | May June
48 | 1 5 10 22 1 5
49 | ----- ------------- ------
50 | ------------
51 | ```
52 |
53 | * I'd allow zooming.
54 |
55 | * I'd handle an item start date being dragged past the item's end date (and vice versa).
56 |
57 | * I'd support item date editing on mobile.
58 |
59 | * I'd adjust the space-to-date mapping code to make the draggable date controls map precisely onto the grid.
60 |
61 | ## How I made design decisions
62 |
63 | * I designed the first version by sketching on paper. (I spent a while trying to find a nice way to make the timeline vertical, rather than horizontal. This felt like it would be a big win because scrolling through events would be so natural. I could never find a design that a) had room for item names and b) tied an item's date span to its name.)
64 |
65 | * I designed the second version by cutting out strips of paper and laying them out on my desk.
66 |
67 | * I hacked together implementations of ideas and then tried them out.
68 |
69 | * I got my wife to try out the interface and used her feedback to improve the design. (For example, as a result of this feedback, I moved the row of dates from the bottom to the top.)
70 |
71 | ## How I'd test this
72 |
73 | * I'd use Jest, because it's nicely integrated with React. I'd probably use Enzyme because it has a nice API over testing React components.
74 |
75 | * The componenents are all pure, so I could test them by just passing in props and checking the right HTML was rendered and the right callbacks were called.
76 |
77 | * When testing a component, I'd test the interfaces between it and its sub components. I'd check its sub-components were rendered and were passed the expected props.
78 |
79 | * I'd also have a few integration tests that checked the output from the whole app to check it worked.
80 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "timeline_assignment",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "moment": "^2.24.0",
7 | "react": "16.2.0",
8 | "react-dom": "16.2.0",
9 | "react-scripts": "1.1.0"
10 | },
11 | "devDependencies": {},
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/public/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maryrosecook/timeline/d88afe3cf66d89c3692d04d303ba473da2c6061a/public/demo.gif
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |