I would like to implement an alternate region background to the chart, sort of like a striped table look.
Is there a way for me to dynamically set the regions for each y-axis block? Once I can do this, I can just basically use css to set alternating background colors.
The regions can be set statically like this:
http://c3js.org/samples/region.html
To do it dynamically, you'd just need to use the API:
http://c3js.org/reference.html#api-regions e.g.
chart.regions([
{axis: 'x', start: 5, class: 'regionX'},
{axis: 'y', end: 50, class: 'regionY'}
]);
So to get alternating stripes build up a regions array as so after generating your chart object:
// find range of all data
var allData = d3.merge (chart.data().map(function(d) {
return d.values.map (function(dd) { return dd.value; });
}));
var dataRange = d3.extent(allData);
dataRange.min = Math.min (dataRange[0], 0);
dataRange.extent = dataRange[1] - dataRange.min;
// set number of pairs of stripes
var stripeCount = 5;
var step = dataRange.extent / stripeCount;
var newRegions = [];
// then divide the data range neatly up into those pairs of stripes
d3.range(0,stripeCount).forEach (function(d) {
newRegions.push ({axis: 'y', start: dataRange.min+(step*d), end: dataRange.min+(step*(d+0.5)), class: 'stripe1'});
newRegions.push ({axis: 'y', start: dataRange.min+(step*(d+0.5)), end: dataRange.min+(step*(d+1)), class: 'stripe2'});
});
// set the new regions on the chart object
chart.regions(newRegions);
css:
.c3-region.stripe1 {
fill: #f00;
}
.c3-region.stripe2 {
fill: #0f0;
}
(If, alternatively, you wanted to make a new stripe pair every 10 units on the y scale you would just make step=10; and change the d3.range to d3.range(0,dataRange.extent/step))
I forked off someone's bar chart on jsfiddle and added the striping --> http://jsfiddle.net/k9c0peax/
Related
I know that I can set depth of all bars in Highcharts using depth property in column property of plotOptions likes the following code:
plotOptions: {
column : {
depth: 30
}
}
Or
# in R
hc_plotOptions(column = list(
depth = 30
)
The questions is how can I set different depth for each bar group in a bar chart (not one depth for all)? Solution can be in R (Highcharter) or in JS?
In core code the depth property is always taken from the series object options. Every group consists of the points with the same x values.
These 2 solutions came to my mind:
1. Modify the core code so that depth values are taken from points' configuration instead:
(function(H) {
(...)
H.seriesTypes.column.prototype.translate3dShapes = function() {
(...)
point.shapeType = 'cuboid';
shapeArgs.z = z;
shapeArgs.depth = point.options.depth; // changed from: shapeArgs.depth = depth;
shapeArgs.insidePlotArea = true;
(...)
};
})(Highcharts);
Series options:
series: [{
data: [{y: 5, depth: 50}, {y: 2, depth: 100}]
}, {
data: [{y: 13, depth: 50}, {y: 1, depth: 100}]
}]
Live demo: http://jsfiddle.net/kkulig/3pkon2Lp/
Docs page about overwriting core functions: https://www.highcharts.com/docs/extending-highcharts/extending-highcharts
2. Create a separate series for every point.
depth property can be applied to a series so the modification of the core wouldn't be necessary. Every series is shown in legend by default so series will have to be properly connected using linkedTo property (so that the user doesn't see as many series as points).
Points can be modified before passing them to the chart constructor or dynamically handled in chart.events.load.
Live demo: http://jsfiddle.net/kkulig/37sot3am/
load: function() {
var chart = this,
newSeries = [],
merge = Highcharts.merge,
depths = [10, 100]; // depth values for subsequent x values
for (var i = chart.series.length - 1; i >= 0; i--) {
var s = chart.series[i];
s.data.forEach(function(p, i) {
// merge point options
var pointOptions = [merge(p.options, {
// x value doesn't have to appear in options so it needs to be added manually
x: p.x
})];
// merge series options
var options = merge(s.options, {
data: pointOptions,
depth: depths[i]
});
// mimic original series structure in the legend
if (i) {
options.linkedTo = ":previous"
}
newSeries.push(options);
});
s.remove(true);
}
newSeries.forEach((s) => chart.addSeries(s));
}
API reference:
https://api.highcharts.com/highcharts/plotOptions.series.linkedTo
I'm new to dc.js and trying to implement a something like the "Monthly Index Abs Move" graph in the demo at https://dc-js.github.io/dc.js/
(see document source at https://dc-js.github.io/dc.js/docs/stock.html).
ie. I'm trying to implement a line chart for "zoom in" view with a bar chart for the "zoomed out" view (rangeChart).
My problem is that when I filter a date range (eg. by using the "brushOn" the bar chart) then the bars that are filtered out disappear
The demo has this working correctly - the bars outside the date range are gray and those within the date range are blue - see screenshots.
I'm using the css file used in the demo, and I'm using very similar code (see code below), so I'm not sure why this difference.
var maxDate = new Date(1985, 0, 1);
var minDate = new Date(2200, 12, 31);
events.forEach(function (d) {
d.created = new Date(d.created);
//d.last_modified = new Date(d.last_modified);
d.hour = d3.time.hour(d.created); // precaclculate for performance
d.day = d3.time.day(d.created);
if (d.created > maxDate) {
maxDate = d.created;
}
if (d.created < minDate) {
minDate = d.created;
}
});
var ndx = crossfilter(events);
var dateDimension = ndx.dimension(dc.pluck('created'));
var chatHourDim = ndx.dimension(dc.pluck('hour'));
var chatDayDim = ndx.dimension(dc.pluck('day'));
var chatsPerHourGroup = chatHourDim.group().reduceCount();
var chatsPerDayGroup = chatDayDim.group().reduceCount();
visitorsPerHour /* dc.lineChart('#visitors-count', 'chartGroup'); */
.renderArea(true)
.width(900)
.height(200)
.transitionDuration(10)
.margins({top: 30, right: 40, bottom: 25, left: 40})
.dimension(chatHourDim)
.mouseZoomable(true)
// Specify a “range chart” to link its brush extent with the zoom of the current “focus chart”.
.rangeChart(visitorsPerDay)
.x(d3.time.scale().domain([minDate, maxDate]))
.round(d3.time.hour.round)
.xUnits(d3.time.hours)
.elasticY(true)
.renderHorizontalGridLines(true)
.legend(dc.legend().x(650).y(10).itemHeight(13).gap(5))
.brushOn(false)
.group(chatsPerHourGroup, 'Chat events per hour')
.title(function (d) {
var value = d.value;
if (isNaN(value)) {
value = 0;
}
return dateFormat(d.key) + '\n' + value + " chat events";
});
// dc.barChart("visitors-count-per-day", 'chartGroup');
visitorsPerDay.width(900)
.height(40)
.margins({top: 0, right: 50, bottom: 20, left: 40})
.dimension(chatDayDim)
.group(chatsPerDayGroup)
// .centerBar(true)
.gap(1)
.brushOn(true)
.x(d3.time.scale().domain([minDate, maxDate]))
.round(d3.time.day.round)
.alwaysUseRounding(true)
.xUnits(d3.time.days);
The way dc.js and crossfilter ordinarily support this functionality is that a crossfilter group does not observe its own dimension's filters.
The range chart example in the stock example uses the same dimension for both charts (moveMonths). So, when the focus chart is zoomed to the selected range in the range chart, it does filter the data for all the other charts (which you want), but it does not filter the range chart.
If you want to use different dimensions for the two charts, I can see a couple ways to get around this.
Using a fake group
Perhaps the easiest thing to do is snapshot the data and disconnect the range chart from later filters, using a fake group:
function snapshot_group(group) {
// will get evaluated immediately when the charts are initializing
var _all = group.all().map(function(kv) {
// don't just copy the array, copy the objects inside, because they may change
return {key: kv.key, value: kv.value};
});
return {
all: function() { return _all; }
};
}
visitorsPerDay
.group(snapshot_group(chatsPerDayGroup))
However, the range chart also won't respond to filters on other charts, and you probably want it to.
Same dimension, different groups
So arguably the more correct thing is to use only one time dimension for both the focus and range charts, although it kills the optimization you were trying to do on binning. A group optionally takes its own accessor, which takes the dimension key and produces its own key, which must preserve the ordering.
Seems like it was probably designed for exactly this purpose:
var dateDimension = ndx.dimension(dc.pluck('created'));
var chatsPerHourGroup = dateDimension.group(function(d) {
return d3.time.hour(d);
}).reduceCount();
var chatsPerDayGroup = dateDimension.group(function(d) {
return d3.time.day(d);
}).reduceCount();
visitorsPerHour /* dc.lineChart('#visitors-count', 'chartGroup'); */
.dimension(dateDimension)
.group(chatsPerHourGroup, 'Chat events per hour')
visitorsPerDay.width(900)
.dimension(dateDimension)
.group(chatsPerDayGroup)
I don't know if you'll notice a slowdown. Yes, JavaScript date objects are slow, but this shouldn't be an issue unless you are converting tens or hundreds of thousands of dates. It's usually DOM elements that are the bottleneck in d3/dc, not anything on the JavaScript side.
I have a page displaying multiple charts. I set the theme to be used for all charts according to the code below:
Highcharts.theme = {
colors: ["#E37B25", "F5BF36", "#7C1C1D", "#458744", "#3E7FA3", "#0C9BD7", "#265667"],
...
}
Highcharts.setOptions(Highcharts.theme);
I then have a bunch of specific options for each chart and create the charts in a set interval.
var chart;
chart = new Highcharts.Chart(chartOptions);
The problem is that every time a chart is created it has the first color in the array, instead of rotating through them. I thought setOptions() would manage this for each chart but apparently it doesn't. How can this be done so every time i create a new chart using new Highcharts.Chart(chartOptions) it selects the next color in the array?
As #wegeld commented, colors in the array are given as list option which are used by series elements of the chart rather than charts in successive manner.
But if that is the requirement, you can create a colorIterator and do following. I have create a codepen example for you here: https://codepen.io/mikhilraj/pen/KrjzxN/
var colorIterator = 0;
function getColor() {
var color = Highcharts.getOptions().colors[colorIterator%Highcharts.getOptions().colors.length];
colorIterator++;
return color;
};
And add following in as options while creating the chart.
series: [{
name: 'John',
data: [0, 1, 4, 4, 5, 2, 3, 7],
fillColor: getColor()
}]
In this jsfiddle the chart has a nice zoom effect every time the visiblity of a series is toggled.
But when I add the UpperLimit series this effect is lost because that series has the lowest and highest x-values.
How can I make the chart zoom in on the series of my choice and keep other series from affecting zoom boundaries?
{
name: 'UpperLimit',
color: '#FF0000',
dashStyle: 'ShortDash',
showInLegend: false,
//affectsZoomBox: false, //No such thing :(
data: [
[1, 100],
[10, 100]
]
},
I don't think it is possible using configuration of the series. However, if you are willing to hack a bit it will be possible to exclude one or more series from the calculation of axis extremes:
chart.xAxis[0].oldGetSeriesExtremes = chart.xAxis[0].getSeriesExtremes;
chart.xAxis[0].getSeriesExtremes = function() {
var axis = this;
var originalSeries = axis.series;
var series = [];
for (var i = 0; i < originalSeries.length; i++) {
// Filter out the series you don't want to include in the calculation
if (originalSeries[i].name != 'UpperLimit') {
series.push(originalSeries[i]);
}
}
axis.series = series;
axis.oldGetSeriesExtremes();
axis.series = originalSeries;
}
The code shows how to overwrite the getSeriesExtremes function on the axis object. The method is replaced with a wrapper that removes the series that should be excluded, then calls the original function and then restores the original series to the axis.
This is clearly a hack. However, it works on my machine...
I have one long unixtime, value Array which is used to initiate a flot chart, and some buttons to change the scale, what I can't seem to be able to do is get Y-axis to scale with the change in X-scale.
Here is an example chart:
http://jsfiddle.net/U53vz/
var datarows = //Data Array Here
var options = { series: { lines: { show: true }, points: { show: true } },
grid: { hoverable: true,clickable: false, },
xaxis: { mode: "time", min: ((new Date().getTime()) - 30*24*60*60*1000), max: new Date().getTime(), }
};
function castPlot() {
window.PLOT = $.plot("#placeholder", [{ data: dataRows }], options
);
};
In the official example scaling is automatic and unspecified on the Y-axis:
http://www.flotcharts.org/flot/examples/axes-time/index.html
The only alternative I can think of is looping through the dataset and calculating new Y min/max on each button press. Unless I am breaking some very obvious default function.
When calculating y-scale, flot does not look at only the "viewable" data but the whole dataset. Since the data points are still present, the y min/max respects them. So your options are:
Subset the series data down to the desired range and let flot scale both x and y.
As you suggested, calculate your own min/max on the y axis.
If you plot get any more complicated than it is now (especially if you start setting up click/hover events on it), I would also recommend you switch to redrawing instead of reiniting your plot.
var opts = somePlot.getOptions();
opts.xaxes[0].min = newXMin;
opts.xaxes[0].max = newXMax;
opts.yaxes[0].min = newYMin;
opts.yaxes[0].max = newYMax;
somePlot.setupGrid();
somePlot.draw();
EDITS
Here's one possible solution.