I have a bar chart in c3js that uses category ticks on the x-axis.
The tick values I want to display are set upon loading the chart:
axis: {
x: {
tick:{
rotate: -35,
values: getTicks(displayData), //Return the tick values
multiline:false
},
type: 'categorized'
}
}
The reason for setting the ticks manually on load, is I need to update them later.
I need to allow users to update the x axis range dynamically, without loading new data, and I want the number of ticks displayed to remain the same, but with different values obviously.
To change the x axis range, I use this function:
chart.axis.range({min: {x: minRange}, max: {x: maxRange}});
There is no function called chart.axis.values.
Any ideas on how to change tick values dynamically?
EDIT -
To be really clear, I do not wish to update the chart's values. That includes the x and y axis values. I only wish to change what ticks are displayed.
Something like this should work.
axis: {
x: {
type: 'timeseries',
tick: {
values: function(x) { return customTicks(x) },
format: function(x) { return customFormat(x); },
}
}
}
customTicks() must return an array of Dates for this to work.
function customTicks(x) {
start = x[0];
end = x[1];
result = [];
for (var i = new Date(start); i <= end; i.setDate(i.getDate() + 1)) {
result.push(new Date(i));
}
return result;
}
customFormat() takes the date and returns the string for that date. This gives you the flexibility to customize the format as needed.
function customFormat(x) {
}
It may not be particularly elegant but does the job. One benefit of this approach is that you can use the same functions on more than one chart and they will all exhibit identical behavior always.
Problem #1 with this approach is that it seems to call customTicks() for every data point just as it does for customFormat().
Problem #2 is that it generates a lot of these errors:
d3.v3.min.js:1 [Violation] Added non-passive event listener to a scroll-blocking 'touchstart' event. Consider marking event handler as 'passive' to make the page more responsive. See https://www.chromestatus.com/feature/5745543795965952
This has already been flagged on Stackoverflow at Added non-passive event listener to a scroll-blocking 'touchstart' event
I am having the same difficulty updating the Y-Axis ticks values dynamically. I searched through the c3.js documents and was expecting to get something like chart.axis.values similar to chart.axis.range or chart.axis.max / chart.axis.min.
After digging into the Chart object on the console. I somehow managed to resolve my problem, but with a bit hacky way.
Please check the fiddle, I have played with the chart.internal which contains the chart's values on which graph is plotted. I changed those values and used chart.flush() method to redraw the chart.
According to https://github.com/c3js/c3/issues/827:
chart.internal.config.axis_x_tick_values = getTicks(displayData);
This works for me.
Related
I have been trying to make a bar chart where I present data from a data set that has several ocurrences of years. I want to show the year in axis X and on axis Y show the number of occurrences of every year. I have been trying the following code on Observable, but I am not sure why it is not working. I tried checking other S.O. posts but couldnt succeed.
There are a few issues going on here that largely stem from the fact that you are working within an Observable notebook, rather than a vanilla Javascript environment. I think it's worth taking a good look at Learning Observable to make sure you understand the basics.
In particular, you can't type:
let x = 1
let y = 2
into a single cell. This is effectively what you're trying to do in your main cell that defines the chart and you get a syntax error. What you might do instead is type:
x = 1
into one cell and then
y = 2
into a different cell. You could then reference those values in other cells.
You also might combine things like:
x_plus_y = {
let x = 1;
let y = 2;
return x+y;
}
The value of x_plus_y would then be available for use in other cells.
Now, I suppose what you're trying to create a count by year of astronaut missions using https://think.cs.vt.edu/corgis/csv/astronauts/.
Assuming so, this Observable Notebook shows a couple of ways to accomplish this.
Finally, I think that it is a mistake to use the BarChart component as you are trying to do. That component was really more of an experimental stepping stone to their more complete, higher level plotting library called Observable Plot. I think it's much easier to get where you want with Plot and you have more control. It' also easy to use Plot in a vanilla environment, like so:
let div = d3.select('#plot')
fetch(
"https://think.cs.vt.edu/corgis/datasets/csv/astronauts/astronauts.csv"
).then(async function (r) {
let astronauts = d3.csvParse(await r.text(), d3.autoType);
div.append(() => Plot.plot({
y: {
grid: true
},
x: { tickFormat: (y) => (y % 10 == 0 ? d3.format('d')(y) : "") },
marks: [
Plot.barY(astronauts, Plot.groupX({ y: "count" }, { x: "Mission.Year" })),
Plot.ruleY([0])
]
}))
});
<script src="https://cdn.jsdelivr.net/npm/d3#7"></script>
<script src="https://cdn.jsdelivr.net/npm/#observablehq/plot#0.6"></script>
<div id="plot"></div>
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.
Marion's fiddle, which perfectly shows multiple distinct points for multiple series.
Following this, I'm trying to implement the same in my fiddle.
I have 2 Name fields PaidDataAxis and DeniedDataAxis
I'm returning series based on this only:
customizeSeries: function(seriesName) {
if (seriesName === 'PaidDataAxis') {
return {
axis: 'paid'
}
} else {
return {
axis: 'denied'
}
}
}
MarkerType which is a tagField is not required.
Both the series along with their points are getting rendered. However, I see that the points are overlapping.
How to avoid series points overlapping in dxChart in this case?
There's something stupid I'm doing, but unable to figure out where.
The solution in such case is to apply min on valueAxis and/or argumentAxis
valueType: 'numeric', //for staying safer
min: 0,
Without which, dxChart will try to automatically calculate axes' scale
I found this live updating time series chart and I’m trying to add a simple feature. I want to add a vertical line where the lowest point on the chart is. I found some code to add a vertical line but I’m not sure how to approach finding the lowest point and than updating the chart. look at this Fiddle, I’m feel like I'm pretty close.
Highcharts.js code to add vertical line
xAxis: {
type: 'datetime',
tickPixelInterval: 150,
plotLines: [{ // this code adds a vertical line
color: '#FF0000', // Red
width: 2,
value: (new Date).getTime() // Position, you'll have to translate this to the values on your x axis
}]
},
This was a truly worthy question that I enjoyed researching. I did some sleuthing and discovered a way to add a plotline whenever the lowest value to date in the time series is generated.
First, I set a variable called lowestValue outside of your chart options. This variable will be used and checked as new points are added to the series.
// make the initial value higher that the possible maximum y value
var lowestValue = 10;
Next, I added some code to your chart events that checks to see whether the new y value that's generated is lower than the current value of lowestValue. If it is, we'll add a new plot line affixed to that point.
I also added code to remove the last plot line that was created (if there was one) to clearly show which point has the lowest value thus far. The key to doing this is to give the plot line you're adding a consistent id. This way, the removePlotLine() function knows which one to remove.
events: {
load: function () {
// set up the updating of the chart each second
var series = this.series[0];
setInterval(function () {
var x = (new Date()).getTime(), // current time
y = Math.random();
series.addPoint([x, y], true, true);
// if the most recent point is lower than
// the last recorded lowest value, add a
// new plotline
if (y < lowestValue) {
lowestValue = y;
var plotOption = {
color: '#FF0000',
width: 2,
value: x,
zIndex: 0,
id: 'thisPlotLine'
};
// remove the previous plot line
series.xAxis.removePlotLine('thisPlotLine');
// add the new plot line
series.xAxis.addPlotLine(plotOption);
}
}, 1000);
}
}
I modified your fiddle to show these changes: http://jsfiddle.net/brightmatrix/pnL6xtLb/2/
Now, you'll notice that, over time, your plot lines may appear more infrequently as the value of lowestValue gets closer to 0. If you'd rather show the lowest value among the points visible in the chart at any given time, I'd suggest adding a counter that keeps track of the number of points that have been added since the last plot line was added. Then, if the lowest point (with its plot line) is moved off the chart, reset lowestValue back to 10 so the lowest visible point is the one that gets the plot line.
I hope this information is helpful to you!
I am trying to plot 3 different series on the same plot. I want to do the following in the plot:
allow the user to select which series are plotted ("Turning series on/off" example on the flot website),
allow the user to zoom in and zoom out ("Rectangular selection with zooming" example),
show the values of the curves when the user hovers the mouse ("Tracking curves" example).
I have been trying to make it work for a long time now, and I can't get everything to work. My current attempt fails to properly turn series on/off. As soon as I check a box, the graph becomes blank. The javascript file which defines all the plotting functions etc., is plot_displacement.js.
The relevant part of the code is:
var choice_container = jQuery("#choices");
choice_container.find("input").change(do_plot);
The HTML is:
<p id="choices">Show:</p>
<div id="plotdiv" style="width:600px;height:300px;position:static;"></div>
I am using change and not click because of this question's answer. If I use click, then selecting/unselecting the checkboxes doesn't seem to have any effect.
I am sure I am overlooking something simple, but I can't figure out what. I would love to be able to do all the 3 things that I am trying to do in flot.
Thanks!
Your problem is simply that do_plot is being called from your change event with xmin being a jQuery Event object. You expect it to be undefined and then set to 0, but instead you're passing xmin: jQuery.Event to flot, which doesn't know what to do with it.
Edit: Since someone asked, the actual code you might want to put in there, when checking if xmin is undefined, additionally check whether it is an object (jQuery.Event has a typeof object):
if (typeof(xmin) == 'undefined' || typeof(xmin) == 'object') xmin = 0;
Click does work for me.
I use the code from the Flot example:
$("#checkboxes").find("input").click(plotAccordingToChoices);
Clicking the checkboxes calls:
function plotAccordingToChoices() {
var data = [];
$("#checkboxes").find("input:checked").each(function () {
var key = $(this).attr("name");
if (key && datasets[key])
data.push(datasets[key]);
});
if (data.length > 0){
plot = $.plot($("#chart1"), data, options);
}
}
datasets is a global array keyed by the set name and was created at initialization.