The ZAM-Calendar is a plugin to show events across time. A vinoez example:
Rather a classic calendar layout. Each event is shown as an event card. In above implementation (used in vinoez's Harvest Summary Calendar) events are individual loads, scheduled across date (x axis) and time (y axis). Other x dimensions can be implemented. Each event has a main part (red or beige) showing additional info and color encoding the grape type (R, W) and a side part (shades of blue) visualizing the status of the load: Confirmed → Blocked → Loaded. The x axis holds additional information on the number of loads and the tons per x unit (day in above example).
The layout arranges the cards so that at least the side part of each card is shown when times overlap within a day. Cards overlap in buckets with increasingly indented cards until a newly added card's start time is equal or greater to the end time of this bucket's first card.
- Zoom/Pan
- Card drag
- Card resize (bottom resize only)
- Hover over card shows tooltip with additional info
Zoom, pan, drag and resize are implemented in the plugin, hover interaction can be implemented application side.
The recommended way is to install the latest release with npm
via git+ssh
.
Direct install:
npm install git+ssh://[email protected]/zamapps/zam-d3-calendar.git#v1.0.0
Indirect package.json
install:
"dependencies": {
"zam-calendar": "git+ssh://[email protected]/zamapps/zam-d3-calendar.git#v1.0.0"
}
Then run the following from your (e.g.) vinoez
directory:
npm install zam-calendar
Whichever install method you use you might have to build it into your workflow. In ZAM apps you would also need to do:
grunt concat
The workflow:
- Load data.
- Create and configure a calendar instance.
- Calculate a calendar layout.
- Build the calendar by mounting it to a DOM element.
The workflow in code:
// 1 Load and prep your data (not shown).
const data = loadAndPrepData(rawData);
// 2. Create and configure a calendar instance.
const calendarInstance = zamCalendar.calendar()
// General and Dimensions:
.parent('#container')
.margin(margin)
.xUnitWidth(300)
.yUnitHeight(60)
.indent(0.06)
.ySnapStep(15)
// Properties:
.xTimeProp('date')
.xPointProp('slot')
.yStartProp('time_start')
.yEndProp('time_end')
.yDurationProp('time_duration')
.eventStatusProp('status_code')
.eventTypeProp('color')
.eventTitleProp('block_code')
.eventSubtitleProps(eventSubtitle)
.quantityProp('quantity')
.eventMainColorMap(typeColorMap)
.eventSideColorMap(statusColorMap)
// Other:
.tooltip('#calendar-tip')
.dispatcher(dispatch);
// 3. Calculate a calendar data layout.
const layoutData = calendarInstance.layout(data, 'date');
// 4. Build the calendar
calendarInstance('#container', layoutData);
No reason to pass out due to the quantity of configurations. Just copy this whole thing into your app and adapt them with the help of below API reference.
-
Data structure: Array of objects.
-
Required object properties and types:
- date property as JS date object
- time start property as JS date object
- time end property as JS date object
- time duration property in minutes as number
- slot property as string
- id property as a number
Further properties as defined in Data Properties below.
The plugin code exposes the zamCalendar
object, you can use to produce calendar instances with.
# zamCalendar.calendar()
Returns a calendar generator called calendar in the following. The calendar can be configured using a chained syntax. Once configured you first augment your data with a layout function and then build the calendar with the main build function.
Each calendar instance exposes two functions:
- The data layout function that takes raw data and produces a pixel position layout.
- The calendar build function that takes in the layout data to produce the calendar.
# calender.layout(⟨ data, view property ⟩)
⟨ data ⟩ is an array of objects, each representing a calendar event to display.
⟨ view property ⟩ defines what type of x axis will be produced, a continuous date axis or a discrete axis. As of now, view property can either be 'slot'
or 'date'
. The chosen view property also needs to be defined in xPointProp or xTimeProp, which determines the variable driving the x axis. This set-up allows toggling between two different x axes.
This layout function returns the original data, augmented with a layout object holding all necessary information to position the event within the bounds of the chart:
# calender(⟨ layout data ⟩)
⟨ layout data ⟩ is an array of objects as returned by the calender.layout() function.
calender()
builds an SVG rendered calendar as a child to the specified parent
element:
Before running above functions the calendar instance needs to be configured with the following values.
# calender.parent(⟨ string ⟩)
Required. ⟨ string ⟩ must be set as the class or, preferably, the id name of the element the calendar-chart should be mounted on.
# calendar.margin(⟨ object ⟩)
Optional. ⟨ object ⟩ can be set as a margin object defining top, bottom, right and left margins. If unspecified it defaults to:
{ top: 85, right: 0, bottom: 0, left: 80 }
The extended left and top margin is necessary for the two axes to be shown.
# calendar.xUnitWidth(⟨ number ⟩)
Required. ⟨ number ⟩ specifies the width of the x axis unit in pixel. Above example has day as the x unit and an xUnitWidth of 300 set with:
calendar.xUnitWidth(300)
As a result each x axis tick will be 300px wide.
# calendar.yUnitHeight(⟨ number ⟩)
Required. ⟨ number ⟩ specifies the height of the y axis unit in pixel. For example above example has hour as the y unit and an yUnitHeight of 60. As a result each major y axis tick will be 60px wide.
# calendar.indent(⟨ number ⟩)
Required. ⟨ number ⟩ specifies how much space of the total x unit width the side card gets allocated. Effectively, it defines the width of the side bar.
It is calculated as indent * xUnitWidth and as such is measured as a fraction of the xUnitWidth.
# calendar.ySnapStep(⟨ number ⟩)
Optional. ⟨ number ⟩ specifies the resolution in minutes, the user can move the event cards in y direction. In other words, it defines at which minute step the event cards will "snap" into position. Defaults to 15.
# calendar.xTimeProp(⟨ string ⟩)
Required. ⟨ string ⟩ specifies the property name of the date property in the original data that defines the date x axis value. An example would be:
calendar.xTimeProp('date')
The implementation allows alternating between one time and one discrete variable to be represented on the x axis. Read on for the discrete property:
#calendar.xPointProp(⟨ string ⟩)
Required. ⟨ string ⟩ specifies the property name of the discrete property in the original data that defines the discrete x axis value.
# calendar.yStartProp(⟨ string ⟩)
Required. ⟨ string ⟩ specifies the name of the property that defines the start time of each event card in the original data. All event cards y start position will be based on the given data property. An example would be
calendar.yStartProp('time_start')
# calendar.yEndProp(⟨ string ⟩)
Required. ⟨ string ⟩ specifies the name of the property that defines the end time of each event card in the original data. All event cards y end position will be based on the given data property. The duration will be calculated by this end and the previous start time property.
#calendar.yDurationProp(⟨ string ⟩)
Required. ⟨ string ⟩ specifies the name of the property that defines the duration of each event card in the original data in minutes. In fact, this configuration has no effect on the actual calendar production but is required to change on data update (drag and resize).
# calendar.eventTypeProp(⟨ string ⟩)
Required. ⟨ string ⟩ specifies the name of the property that defines the color encoding of the main card in the original data.
# calendar.eventStatusProp(⟨ string ⟩)
Required. ⟨ string ⟩ specifies the name of the property that defines the color encoding and labelling of the side card in the original data.
# calendar.eventTitleProp(⟨ string ⟩)
Required. ⟨ string ⟩ specifies the name of the property that will be used as the main title of the the main card in the original data.
# calendar.eventSubtitleProps(⟨ Array ⟩)
Required. ⟨ Array ⟩ is an array of objects specifying which properties of the original data should be displayed as subtitle information on the main event card.
The ⟨ Array ⟩ objects need to specify the variable name, the display name and the level (level 1 for the first row and level 2 for the second row of the subtitle). An example of an eventSubtitle array producing above event card would look like so:
const eventSubtitle = [
{variable: 'crush_site', name: 'Crush site', level: 1},
{variable: 'pick_method_id', name: 'Pick method', level: 2},
{variable: 'varietal_code', name: 'Varietal', level: 2},
{variable: 'quantity', name: 'Tons', level: 2}
]
# calendar.quantityProp(⟨ string ⟩)
Required. ⟨ string ⟩ specifies the name of the property that will be displayed as additional information per x unit on the x axis.
The property values need to be numeric.
# calendar.eventMainColorMap(⟨ Map ⟩)
Required. ⟨ Map ⟩ specifies the background and the text colors to be used for the main card events. Allocates the respective colors based on the eventTypeProp. An example:
const typeColorMap = new Map();
typeColorMap.set('R', { back: '#993355', text: '#fff' });
typeColorMap.set('W', { back: '#ffdba0', text: '#333' });
calendar
.eventTypeProp('color')
.eventMainColorMap(typeColorMap)
// (...)
Assuming 'color' has two categories: 'R' and 'W', the background and text colours will now be respectively defined by the typeColorMap.
# calendar.eventSideColorMap(⟨ Map ⟩)
Required. ⟨ Map ⟩ specifies the background and the text colors to be used for the side card events. Allocates the respective colors based on the eventStatusProp. An example:
const statusColorMap = new Map();
statusColorMap.set('C', { back: '#ddf1fc', text: '#006699' });
statusColorMap.set('B', { back: '#55bbee', text: '#fff' });
statusColorMap.set('L', { back: '#006699', text: 'rgba(255,255,255,0.8)' });
calendar
.eventStatusProp('status_code')
.eventSideColorMap(statusColorMap)
// (...)
Assuming 'status_code' has three categories: 'C', 'B' and 'L', the background and text colours will now be respectively defined by the statusColorMap.
### Other configurations
# calendar.tooltip(⟨ string ⟩)
Required. ⟨ string ⟩ specifies the class or, preferably, the id name of the calendar tooltip element. The tooltip is being built in the application code, but will be toggled in the calendar plugin in some cases (during resize, drag).
# calendar.dispatcher(⟨ d3.dispatch ⟩)
Optional. ⟨ d3.dispatch ⟩ enables the calendar and the calling application code to be loosely coupled. The dispatch is used to trigger data update events from the calendar which are handled in the application code. If inclined, see the D3 documentation of dispatch for more details.
If any problems check Stack Overflow for zam-calendar, file an issue at https://github.com/zamapps/zam-d3-calendar/issues or reach me at [email protected].