dc.js line chart starting from zero after each point - javascript

I have a line chart as shown in the fiddle http://jsfiddle.net/djmartin_umich/qBr7y/
The graph works fine and plots as expected. But I need one change to be made so that the plots become triangular shaped and I could see a series of irregular triangles. I mean after every point in Y, it should drop to 0 and start afresh. I know we could achieve this by explicitly adding data points to point to 0. But, just wondering if we could do that without creating additional data points.
HTML:
<div id="line-chart"></div>
<div id="log">Incoming Data:</div>
JS:
var startDate = new Date("2011-11-14T16:17:54Z");
var currDate = moment(startDate);
var cf = crossfilter([{date: startDate, quantity: 1}]);
AddData();
var timeDimension = cf.dimension(function(d){ return d.date; });
var totalGroup = timeDimension.group().reduceSum(function(d){ return d.quantity; });
var lineChart = dc.lineChart("#line-chart")
.brushOn(false)
.width(800)
.height(200)
.elasticY(true)
.x(d3.time.scale().domain([startDate, currDate]))
.dimension(timeDimension)
.group(totalGroup);
dc.renderAll();
window.setInterval(function(){
AddData();
lineChart.x(d3.time.scale().domain([startDate, currDate]));
dc.renderAll();
}, 800);
function AddData(){
var q = Math.floor(Math.random() * 6) + 1;
currDate = currDate.add('day', 5);
cf.add( [{date: currDate.clone().toDate(), quantity: q}]);
$("#log").append(q + ", ");
}
CSS:
#log{
clear:both;
}
Thanks,
Vicky

You can use a "fake group" to achieve this effect.
This is a general-purpose technique for preprocessing data that allows you to change what the chart sees without modifying the data in the crossfilter. In this case, we want to add a data point immediately after each point returned by the crossfilter group.
The fake group wraps the crossfilter group in an object that works like a group. Since in most cases dc.js only needs to call group.all(), this is pretty easy:
function drop_to_zero_group(key_incrementor, group) {
return {
all: function() {
var _all = group.all(), result = [];
_all.forEach(function(kv) {
result.push(kv);
result.push({key: key_incrementor(kv.key), value: 0});
})
return result;
}
}
}
The fake group here produces two data points for each one it reads. The first is just a duplicate (or reference) of the original, and the second has its key incremented by a user-specified function.
It might make sense to parameterize this function by the zero value as well, but I mostly wanted to pull out the date incrementor, since that involves another trick. Here is a date incrementor:
function increment_date(date) {
return new Date(date.getTime()+1);
}
This uses date.getTime() to get the integer value (in milliseconds since the beginning of 1970), adds one, and converts back to a date.
Actually, the first time I tried this, I forgot to include +1, and it still worked! But I don't recommend that, since dc.js is likely to get confused if there is more than one point with the same x value.
Apply the fake group by wrapping the group before passing it to the chart
lineChart
.group(drop_to_zero_group(increment_date, totalGroup));
Here's a fork of DJ's fiddle: http://jsfiddle.net/gordonwoodhull/dwfgma8j/4/
FWIW I also changed dc.renderAll() to dc.redrawAll() in order to enable animated transitions instead of blinking white and rendering from scratch each time. The transitions are not perfect but I think it's still better than the blink. I have a fix but it's a breaking change so it will go into dc.js 2.1.

Related

Is there a way to be sure that the mosiaic you're creating with help of Googles Earth Engine is complete?

As stated in the heading - we're trying to use the data out of the mosaic for a project but the data will only make sense when the mosaic is actually completed. So if the area we choose is too big, the satellite might not get over the spot in the span of a day which would make the result incomplete.
So is there a way to only get the results of 100% complete mosaics?
Here is the code we're using:
// Difference in days between start and finish
var diff = finish.difference(start, 'day');
// Make a list of all dates
var range = ee.List.sequence(0, diff.subtract(1)).map(function(day){return start.advance(day,'day')});
// Funtion for iteraton over the range of dates
var day_mosaics = function(date, newlist) {
// Cast
date = ee.Date(date);
newlist = ee.List(newlist);
// Filter collection between date and the next day
var filtered = collection.filterDate(date, date.advance(1,'day'));
// Make the mosaic and clip to aoi
var clipped = ee.Image(filtered.mosaic().clip(aoi)).setMulti({'system:time_start': date});
var footprint = ee.Feature(clipped.get('system:footprint'));
// Add the mosaic to a list only if the collection has images
return ee.List(ee.Algorithms.If(footprint.area().eq(aoi.area()), newlist.add(clipped), newlist));
};
// Iterate over the range to make a new list, and then cast the list to an imagecollection
var mosaic = ee.ImageCollection(ee.List(range.iterate(day_mosaics, ee.List([]))));
print(mosaic);
You've already got the idea of looking at the footprint, but it would be more accurate to
use the mask pixels instead of the footprint geometry, in case there are any holes in the image, and
instead of counting the valid areas, find out if there are any invalid areas.
var missingPixels = image
.mask() // Get the mask data as an image
.reduce(ee.Reducer.min()) // Take the least coverage across all bands
.expression('1 - b(0)') // Invert the value
.rename(['missing_pixels']); // Give the result a better name
// Sum up missing pixel indications; if nonzero, some are missing. Pick a scale that is good enough for your purposes.
var noMissingPixels = missingPixels.reduceRegion({
geometry: aoi,
reducer: ee.Reducer.sum(),
scale: 100
}).getNumber('missing_pixels').eq(0);
print(noMissingPixels);
https://code.earthengine.google.com/2dff320b7ab68e27e3acbd96efa23e4b
Additional advice: iterate() to collect a list result is inefficient and should be avoided whenever practical. When you want to process a collection and discard some elements, you can use map() with the dropNulls parameter set to true, and return null for any element you don't want to keep:
var day_mosaics = function(date) {
... regular code goes here, but no 'newlist' ...
return ee.Algorithms.If(noMissingPixels, clipped, null);
};
var mosaic = ee.ImageCollection(range.map(day_mosaics, true));
This will work well with very large collections whereas building a list may run out of memory.

dc.js lineChart performance issue with 8k+ items

This is my second question on the dc.js/d3.js/crossfilter.js topic. I am trying to realize a basic personal dashboard and I started by creating a very simple lineChart (with a rangeChart associated) that outputs metrics over time.
The data I have is saved as json (it will be stored in a mongoDb instance at a later stage, so for now I used JSON that also keep datetime format) and looks like this:
[
{"date":1374451200000,"prodPow":0.0,"consPow":0.52,"toGridPow":0.0,"fromGridPow":0.52,"prodEn":0.0,"consEn":0.0,"toGridEn":0.0,"fromGridEn":0.0},
{"date":1374451500000,"prodPow":0.0,"consPow":0.34,"toGridPow":0.0,"fromGridPow":0.34,"prodEn":0.0,"consEn":0.0,"toGridEn":0.0,"fromGridEn":0.0},
{"date":1374451800000,"prodPow":0.0,"consPow":0.42,"toGridPow":0.0,"fromGridPow":0.42,"prodEn":0.0,"consEn":0.0,"toGridEn":0.0,"fromGridEn":0.0},
...
]
I have around 22000 entries like this and I am experiencing lot of performance issues when opening the dashboard. Even if I try to slice the data in a set of 8000 records, the performance are still pretty bad (but at least the rendering finishes after some time) and the interaction with the data is awful.
I am guessing that my code has some pitfall that makes it under-perform since I'd expect dc.js and crossfilter.js to struggle with 100k+ entries and more than one dimension!
Nevertheless, profiling with chrome and reading online didn't help much (more details on what I tried to change later).
Here is my graph.js code:
queue()
.defer(d3.json, "/data")
.await(makeGraphs);
function makeGraphs(error, recordsJson) {
// Clean data
var records = recordsJson;
// Slice data to avoid browser deadlock
records = records.slice(0, 8000);
// Crossfilter instance
ndx = crossfilter(records);
// Define Dimensions
var dateDim = ndx.dimension(function(d) { return d.date; });
// Define Groups
var consPowByDate = dateDim.group().reduceSum(function (d) { return d.consPow; });
var prodPowByDate = dateDim.group().reduceSum(function (d) { return d.prodPow; });
// Min and max dates to be used in the charts
var minDate = dateDim.bottom(1)[0]["date"];
var maxDate = dateDim.top(1)[0]["date"];
// Charts instance
var chart = dc.lineChart("#chart");
var volumeChart = dc.barChart('#volume-chart');
chart
.renderArea(true)
/* Make the chart as big as the bootstrap grid by not setting ".width(x)" */
.height(350)
.transitionDuration(1000)
.margins({top: 30, right: 50, bottom: 25, left: 40})
.dimension(dateDim)
/* Grouped data to represent and label to use in the legend */
.group(consPowByDate, "Consumed")
/* Function to access grouped-data values in the chart */
.valueAccessor(function (d) {
return d.value;
})
/* x-axis range */
.x(d3.time.scale().domain([minDate, maxDate]))
/* Auto-adjust y-axis */
.elasticY(true)
.renderHorizontalGridLines(true)
.legend(dc.legend().x(80).y(10).itemHeight(13).gap(5))
/* When on, you can't visualize values, when off you can filter data */
.brushOn(false)
/* Add another line to the chart; pass (i) group, (ii) legend label and (iii) value accessor */
.stack(prodPowByDate, "Produced", function(d) { return d.value; })
/* Range chart to link the brush extent of the range with the zoom focus of the current chart. */
.rangeChart(volumeChart)
;
volumeChart
.height(60)
.margins({top: 0, right: 50, bottom: 20, left: 40})
.dimension(dateDim)
.group(consPowByDate)
.centerBar(true)
.gap(1)
.x(d3.time.scale().domain([minDate, maxDate]))
.alwaysUseRounding(true)
;
// Render all graphs
dc.renderAll();
};
I Used chrome dev tools to do some CPU profiling and as a summary these are the results:
d3_json parsing at the top takes around 70ms (independent from #records)
with 2000 records:
make_graphs takes slightly under 1s;
dimensions aggregated take around 11ms;
groups aggregated take around 8ms;
dc.lineChart take around 16ms;
dc.barChart take around 8ms;
rendering takes around 700ms (450ms for lineChart);
data interaction is not super smooth but it is still good enough.
with 8000 records:
make_graphs takes around 6s;
dimensions aggregated take around 80ms;
groups aggregated take around 55ms;
dc.lineChart take around 25ms;
dc.barChart take around 15ms;
rendering takes around 5.3s (3s for lineChart);
data interaction is awful and filtering takes lot of time.
with all records the browser stalls and I need to stop the script.
After reading this thread I thought it could have been an issue with dates so I tried to modified the code to use numbers instead of dates. Here is what I modified (I will write down only the changes):
// Added before creating the crossfilter to coerce a number date
records.forEach(function(d) {
d.date = +d.date;
});
// In both the lineChart and barChart I used a numeric range
.x(d3.scale.linear().domain([minDate, maxDate]))
Unfortunately nothing noticeable changed performance-wise.
I have no clue on how to fix this and actually I would like to add more groups, dimensions and charts to the dashboard...
Edit:
Here is a github link if you want to test my code by yourself.
I used python3 and flask for the server side, so you just have to install flask:
pip3 install flask
run the dashboard:
python3 dashboard.py
and then go with your browser to:
localhost:5000
It's hard to tell without trying it out but probably what is happening is that there are too many unique dates, so you end up with a huge number of DOM objects. Remember that JavaScript is fast, but the DOM is slow - so dealing with up to half a gigabyte of data should be fine, but you can only have a few thousand DOM objects before the browser chokes up.
This is exactly what crossfilter was designed to deal with, however! All you need to do is aggregate. You're not going to be able to see 1000s of points; they will only get lost, since your chart is (probably) only a few hundred pixels wide.
So depending on the time scale, you could aggregate by hour:
var consPowByHour = dateDim.group(function(d) {
return d3.time.hour(d);
}).reduceSum(function (d) { return d.consPow; });
chart.group(consPowByHour)
.xUnits(d3.time.hours)
or similarly for minutes, days, years, whatever. It may be more complicated than you need, but this example shows how to switch between time intervals.
(I'm not going to install a whole stack to try this - most examples are JS only so it's easy to try them out in jsfiddle or whatever. If this doesn't explain it, then adding a screenshot might also be helpful.)
EDIT: I also notice that your data is integers but your scale is time-based. Maybe this causes objects to be built all the time. Please try :
records.forEach(function(d) {
d.date = new Date(+d.date);
});

dc.js chart groups not updating with correct values on charts after click on a parent chart

My issue here is that I have a crossfilter group reduce function for calculating savings, but when a filter is applied on a chart, it seems to not pass the correct filter across. I also have a similar case working example.
I have created two jsbin's out of which one of them has correct behavior.
I have data for two years in this case 2010 and 2014. But in the Savings(not working) chart the carrier pie chart doesn't filter by year whereas it does in the DIFOT(working) chart.
The links are :
DIFOT(Working) : http://jsbin.com/bagohavehu/2/edit
Savings (Not working as expected) : http://jsbin.com/yudametulo/2/edit
Thanks a lot for your time and effort.
Regards,
Animesh
To track these calculation problems down, you really need to use the debugger and put breakpoints in the reduction functions or in the draw functions to see what reduced values you ended up with.
I find using the browser's debugger very difficult (impossible?) to do in jsbin, so I have again pasted your code into two jsfiddles:
parseFloat version: http://jsfiddle.net/gordonwoodhull/aqhLv0qc/
parseInt version: http://jsfiddle.net/gordonwoodhull/9bnejpLx/1/
Now, we set a breakpoint in chart.redraw (dc.js:1139) to see what values it's trying to plot on clicking the slice.
The first time the breakpoint is hit isn't interesting, because it's just the yearly chart we clicked on, but the second hit is the carrier chart, which reveals a lot. Printing _chart.group().all() for the parseInt version:
Now, for the parseFloat version:
Since the calculations come out exact for the int version, you end up with 1 - 0/0 === NaN, and somewhere along the way dc.js or d3.js silently forces NaN to zero.
But since float calculations are never exact, you end up with 1 - 0/-3e-15 === 1.
You want to avoid dividing by zero, or anything close to zero, so adding a check in your reduction function produces (I think) the desired result:
var group = Dim.group().reduce(
function(p,v){
p.current += parseFloat(v.currentPrice);
p.compare += parseFloat(v.comparePrice);
if(Math.abs(p.compare) > 0.01) {
p.savings = p.current/p.compare;
p.result = 1-p.savings;
}
else p.result = 0;
return p;
},
function(p,v){
p.current -= parseFloat(v.currentPrice);
p.compare -= parseFloat(v.comparePrice);
if(Math.abs(p.compare) > 0.01) {
p.savings = p.current/p.compare;
p.result = 1-p.savings;
}
else p.result = 0;
return p;
},
function(){return { current: 0, compare: 0,savings:0, result:0};}
);
Working version (I think): http://jsfiddle.net/gordonwoodhull/y2sf7y18/1/

How to plot animated line chart using d3 while the data table is required to be updated every 1 second?

I want to simulate the situation where there is continuous incoming real time (dynamic) data and feed to the chart
Therefore, I try to demonstrate an animated line chart using d3 while the data array is required to be continuous updated every 1 second.
The original work is inspired by benjchristensen
And here is my modified html source: my source code
I try to populate the buffer[100] with random number for every 1 second by calling function startGenerator()
function startGenerator()
{
//signalGenerator();
setInterval("signalGenerator()", 1000); //
}
function signalGenerator()
{
var buffer = new Array(100);
var i;
for (i = 0; i < buffer.length; i++)
{
buffer[i] = Math.random() * 10;
}
FeedDataToChart("#graph1", 300, 300, "basis", true, 100, 100, buffer, 0);
}
Below is the FeedDataToChart() parameter names:
function FeedDataToChart(id, width, height, interpolation, animate, updateDelay, transitionDelay, data, startIndex)
I use a counter to check for the re-draw data index, every time when re-draw new data, counter increase by 1. Until the the counter < data.length-1, the re-draw timer should stop and get new data from the buffer[100] again.
function stopTimer()
{
clearInterval(myTimer);
alert("The redraw timer stop here, get new data from buffer");
}
function startTimer()
{
if (myTimer == null)
{
myTimer = setInterval(function() {
if (counter < data.length - 1)
{
var v = data.shift(); // remove the first element of the array
data.push(v); // add a new element to the array (we're just taking the number we just shifted off the front and appending to the end)
if(animate)
{
redrawWithAnimation();
}
else
{
redrawWithoutAnimation();
}
counter++;
}
else
{
alert("no more data in buffer");
stopTimer();
counter = startIndex;
}
}, updateDelay);
}
}
My problem occurs when I tried to repeat the startGenerator() function, the results shows like this:
I am pretty new to javascript. Can anyone pin point me how to pull data from the buffer every 1 second and update the single line chart continuously?
Thank you!
EDIT I update the source and problem has been minimized: My New Source
I add the .remove() line at the stopTimer() to remove the sgv-holded data and reset the global buffer to null and call the startGenerator() function again to pull new data when timer stop.
Now the only problem I having is that everytime when new graph is created, it create a new sgv path where below the previous one. The new graph is moving downwards everytime when it was created. Check the new source I updated today. You will catch my description after you run the code.
How can I fixed the sgv path at the same location everytime when I update it?
The problem with simply calling FeedDataToChart again is that it creates entirely new svg and path elements rather than reusing the existing ones. The inner redraw method you're using follows the standard D3 update pattern, so take that as an example for how to rework the update.
Start by separating initialization from drawing. There's no reason the scale and line generators need to change on every update, so get that out of the way early. Also create the svg element as soon as you can and don't change it unless you really need to. Likewise with the path element.
The general structure of any D3 visualization will consist of three distinct phases:
intialization - perform as soon as possible after script load
create scale and svg generator functions (scale, axis, line, area etc.)
create data cleanup & aggregation functions (nest, custom filters etc.)
creation - perform as soon as possible after DOM ready
create svg element (only ever call .append("svg") once per chart)
create chart and axis groups (see the margin conventions)
draw - perform when data is available
first segment by enter/update/remove selections
find existing data elements (.selectAll() on data element CSS class)
rebind data to chart
perform on enter selection
create chart data elements (only call .append(el) after .enter())
set chart data element CSS class (make this the canonical handle)
set static properties (but if you can put these in CSS all the better)
perform on enter and update selections
set dynamic properties (here is the call to .attr("d", line))
you may also have a remove portion (for exit selections)
For more information take a look at Mike Bostock's tutorial on selections in D3.
The create, update, remove process is actually pretty simple once you get used to it. Here's a prototypical example:
// select existing data elements and rebind data
var dots = d3.selectAll(".dot")
.data(data);
// append a circle for new dots
dots.enter().append("circle")
.attr("class", "dot")
.attr("r", 4);
// remove old dots
dots.exit().remove();
// update all new and existing dots
dots.attr("cx", x)
.attr("cy", y);
The key to writing simple and performant D3 is to minimize the changes you're making to the DOM, since DOM manipulation tends to be the major performance bottleneck.

If elements are touching, split their width in half?

I'm working on a Javascript/jQuery calendar which includes a month view and a day view. Clicking the days will change the date, which will update the date variables in the day view.
The day view is split up into half hour segments from midnight to 11:00 PM. Clicking on any half hour <tr> (the day view is a table) will create an event between that time clicked and an hour in the future, as well as append a div on top of the calendar, spanning the range of time and positioned at the correct starting point (each pixel is a minute...)
There is a problem, however. If you create an "event" between a certain time span where there is already one in place, they overlap. This is the default behavior, obviously, but what I would like to happen is that if an event is created between a range of dates that is already occupied by an event, they align side by side so that they're not overlapping.
This resembles the behavior seen in the iCal app for mac:
Now my first thought to achieve such a goal was to use collision detection, but all the jQuery plugins for this are bloated or require the elements to be draggable.
Then I thought there might be a way in CSS to do this, where if two elements are overlapping, they split the width evenly.
Then I thought that's ridiculously far fetched, so I'm wondering how I can achieve this as easily as possible.
I'll post the full code in a jsFiddle, but for the most important function would be insertEvent which looks like this:
function insertEvent(start, end){
var end_minutes = new Date(end).getMinutes();
var end_border = new Date(new Date(end).setMinutes(end_minutes + 2));
//$(".day_date").html(start + "<br />" + end);
var diff = Math.abs(end_border - new Date(start));
var minutes = Math.floor((diff/1000)/60);
var start_element = $("td").find("[data-date='" + start + "']");
var offset = start_element.offset().top - $(".second").offset().top;
var this_element = $("<div class='event' style='height:" + minutes + "px;margin-top:" + offset + "px;'></div>");
$(".right").prepend(this_element);
}
This takes two parameters in the javascript new Date() format, one for the start date and one for the end date.
The fiddle: http://jsfiddle.net/charlescarver/HwdwL/
One of the the problems I see with your approach is that there isn't a structure to the storage of the data. I've built a calendar in Javascript before and it's not easy work. First, make sure you have some kind of abstraction for the calendar event. Something like:
function CalendarEvent(startDateTime, endDateTime) {
this.startDateTime = startDateTime;
this.endDateTime = endDateTime;
}
CalendarEvent.prototype.start = function() {
return this.startDateTime.getTime();
};
CalendarEvent.prototype.end = function() {
return this.endDateTime.getTime();
};
CalendarEvent.new = function(startDateTime, endDateTime) {
// This is a little factory method. It prevents calendar events
// from having end times that fall before the start time.
// USE THIS TO INSTANTIATE A NEW CALENDAR EVENT
if(endDateTime.getTime() < startDateTime.getTime()) {
throw new Error("End time falls before start time");
}
return new CalendarEvent(startDateTime, endDateTime);
};
CalendarEvent.compare = function(eventOne, eventTwo) {
// this is a class method to compare two events
// If used with sort it will sort by startDateTime
return eventOne.start() - eventTwo.start();
};
// ... add any other methods you need
Next you're going to want to sort the calendar events. I would sort by start time. Then once it is sorted you can actually re-render everything when changes are made. As long as you sort correctly, determining if a calendar event collides is as simple as this:
CalendarEvent.prototype.intersects = function(otherEvent) {
// If the other event starts after this one ends
// then they don't intersect
if(otherEvent.start() > this.end()) {
return false;
}
// If the other event ends before this one starts
// then they don't intersect
if(otherEvent.end() < this.start()) {
return false;
}
// Everything else is true
return true;
};
Because the data is sorted you know that if two or more calendar events intersect they will have to share the space. Granted, you must think about a few things when you divide the space. Do you want a naive implementation where you just share the space equally from left to right (left having the earliest start time). If so your visual representation could look like this if it had 4 events that shared a space (each block is an event):
However if your events have strange shapes they might cause your calendar to look strange. Consider the following:
In this instance event 2 takes up a lot of vertical space and all the space underneath event 1 is unused. Maybe for a better UX you don't want that kind of thing to happen. If so you should design your rendering algorithm accordingly. Just remember that it is probably easiest to re-render on every change that you encounter, but it's all about how you store the data. If you do not store the data in some kind of structure that is easily traversed then you won't be able to do this kind of thing.
But to complete the answer to your question, here is a fairly naive example. I haven't tested it so this is a pretty big assumption of it working. It is not entirely complete you will have to edit the rendering for yourself. This is merely to give you an idea of how to get it to work. It could definitely look prettier:
function renderCalendarEvents(calendarEvents) {
// Sort the calendar events (assuming calendarEvents is an array)
var sortedEvents = calendarEvents.sort(CalendarEvent.compare);
var index = 0;
// renderEvents is an anonymous function that will be called every time
// you need to render an event
// it returns it's columnDivisor.
var renderEvent = function(position) {
var currentEvent = sortedEvents[index];
var nextEvent = sortedEvents[index + 1];
// The default column divisor is determined by
// the current x-position + 1
var columnDivisor = position + 1;
// Increment before any recursion
index += 1;
// Check if nextEvent even exists
if(nextEvent) {
// If the nextEvent intersects with the current event
// then recurse
if(currentEvent.intersects(nextEvent)) {
// We need to tell the next event that it starts at the
// column position that is immediately +1 to the current event
columnDivisor = renderEvent(position + 1);
}
}
// placeEvent() is some function you can call to actually place
// the calendar event element on the page
// The position is the x-position of the current event
// The columnDivisor is a count of the amount of events sharing this column
placeEvent(currentEvent, position, columnDivisor);
return columnDivisor;
};
while(true) {
// render events until we're done
renderEvent(0);
if(index >= sortedEvents.length) {
break;
}
}
}
Essentially the idea with this particular algorithm is that if the nextEvent on the list exists and that event intersects with the currentEvent then we need to split the width of the currentEvent. It keeps on recursing until it finds no more intersections then it makes it's way back up the chain of recursive calls. I skipped the actual DOM manipulation logic because really the hard part is determining how much you need to split the actual column in order to get these events to fit. So hopefully this all makes a little bit of sense.
EDIT:
To be much more clear, in order to add this to your existing code I would replace your insertEvent function with something like this. I don't write all of the logic for you so you'll have to do some of your own writing. But that's half the fun :-).
function insertEvent(start, end) {
var newEvent = Calendar.new(start, end);
// you'll have to store the array somewhere.
// i'm just assuming some kind of global right now
eventsArray.push(newEvent);
// You'll want to destroy any event elements
destroyCurrentEventElements();
// Now run the rendering function
renderCalendarEvents(eventsArray);
}

Categories

Resources