I'm trying to graph out metrics that don't have any relation to one another, so instead of plotting out the actual values, I've calculated an alternate set of values that are scaled between 0-1 like a percentage.
For example: [1, 2, 5] => [0.2, 0.4, 1]
So now I have 2 sets of data - the original and scaled versions. I have the scaled version plotting on to my graph just fine, but I want the tooltip to show the original value to the user. See what I mean?
I checked out http://c3js.org/samples/tooltip_format.html, which shows you can set tooltip as a function when you initially generate the C3 object. But I want to change the tooltip later on after I recalculate my original/scaled values and re-load() the graph.
All attempts I've made to explicitly change myGraph.tooltip.format.value = function (...) {...} after initially setting myGraph = C3.generate({...}) have been unsuccessful.
Any ideas how I can accomplish this without having to regenerate the graph from scratch every time?
You need to override internal.getTooltipContent
var data = ['data1', 30000, 20000, 10000, 40000, 15000, 250000];
// simple fake data
var fakeData = data.map(function (d, i) {
return i ? (d / 100) : d;
})
var chart = c3.generate({
data: {
columns: [
fakeData,
['data2', 100, 200, 100, 40, 150, 250]
],
}
});
// do code to take over mars and plant potatoes
// save the original
var originalGetTooltipContent = chart.internal.getTooltipContent;
chart.internal.getTooltipContent = function (data, defaultTitleFormat, defaultValueFormat, color) {
// we modified the first series, so let's change that alone back
var originalValue = {
id: data[0].id,
index: data[0].index,
name: data[0].name,
// unfaked
value: data[0].value * 100,
x: data[0].x
};
var originalValues = data.map(function (d, i) {
return i ? d : originalValue;
})
return originalGetTooltipContent.call(this, originalValues, defaultTitleFormat, defaultValueFormat, color)
}
I assume you are already doing something about the scaled y axis label?
Fiddle - http://jsfiddle.net/puf248en/
Thanks potatopeelings,
I did turn out solving this one by simply loading the form data in all at once, and then programmatically show/hide certain metrics. So that allowed me to use all the generate() options as intended.
Did try out your solution, and it seemed to do the trick till I found the simpler option. Thanks!
Related
I have been recently experimenting with ChartJS and have come into a problem regarding sorting the bars in descending order, from lowest to highest. I have tried to debug but found no luck.
Are you sure the "beforeupdate" is called when you click the "Sort Data" button, because it seems you use the wrong variable.
barChart → barGraph
UPDATED
The old one link
UPDATED
Here is the link
// Get the data from each datasets.
var dataArray = [];
$.each(chart.data.datasets, function() {
dataArray.push(this.data);
});
// Get the index after sorted.
let dataIndexes = dataArray.map((d, i) => i);
dataIndexes.sort((a, b) => {
return dataArray[a] - dataArray[b];
});
// create after sorted datasets.
var tempDatasets = [];
$.each(dataIndexes, function() {
tempDatasets.push(chart.data.datasets[this]);
});
// apply it
chart.data.datasets = tempDatasets;
What data is returned using Ajax?
data = [{
sample : "ALPINE",
electric : 100,
mpg: 200,
urban: 300,
vmt: 400,
airtravel: 500,
renewable: 100,
conservation : 200,
heating: 300,
water: 400,
cefficiency: 500,
shiftc: 100,
healthyd: 200
}];
I am using a WebSocket connection to update a candlestick chart with live data.
Creating the initial candlestick chart is relatively easy:
var candleDiv = document.getElementById('candle-chart');
var data = {
x: x, //Each of these is a single dimension array of the same length
open: open,
close: close,
high: high,
low: low,
type: 'candlestick',
};
var layout = {
datarevision: candleCount,
dragmode: 'zoom',
showlegend: false,
xaxis: {
type: 'date',
range: [x[x.length - 26], x[x.length - 1]], //Only show the last 25 entries so it's not zoomed out too far.
rangeslider: {
visible: false
},
yaxis: {
autorange: true,
}
}
}
data.xaxis = 'x';
data.yaxis = 'y';
data = [data];
Plotly.plot(candleDiv, data, layout);
However, the documentation for the restyle method doesn't talk much to the update of data. More about how the data is displayed. After much tinkering, I found a reasonable workaround of updating the data variable directly:
candleDiv.data[0].open[candleDiv.data[0].open.length - 1] = updatedOpenValue;
candleDiv.data[0].close[candleDiv.data[0].close.length - 1] = updatedCloseValue;
candleDiv.data[0].high[candleDiv.data[0].high.length - 1] = updatedHighValue;
candleDiv.data[0].low[candleDiv.data[0].low.length - 1] = updatedLowValue;
Plotly.restyle(candleDiv, 'data[0]', candleDiv.data[0], [0]);
This works, except that it appears to draw the new candle on the old candle. This becomes particularly distracting when the stick changes from a green (increasing) stick to a red (decreasing) stick.
Is there a correct syntax to achieve what I am attempting to do such that I don't get display issues?
I checked out this link from this post but I couldn't get the method used to work in the context of a candlestick chart.
You may want to look at the Plotly.react method instead of Plotly.restyle: https://plot.ly/javascript/plotlyjs-function-reference/#plotlyreact
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 trying to set max value axis using dataprovider, since I'm dynamically loading data in my bar chart I need to see the "progress" on one of the bars compared to the one that is supposed to be the total.
How do I achieve this?
I' tried with:
"valueAxes": [
{
"id": "ValueAxis-1",
"stackType": "regular",
"maximum": myDataProviderAttribute
}
But no luck.
Any suggestion will be much apreciated.
I've submited a ticket to AmCharts support and got this feedback:
You can set max before the chart is initialized based on the data you have, inside addInitHandler. Here is an example for a simple column chart:
AmCharts.addInitHandler(function(chart) {
// find data maximum:
var min = chart.dataProvider[0].visits;
for (var i in chart.dataProvider) {
if (chart.dataProvider[i].visits > max) {
max = chart.dataProvider[i].visits;
}
}
// set axes max based on value above:
chart.valueAxes[0].maximum = max + 100;
chart.valueAxes[0].strictMinMax = true;
});
You may need to use strictMinMax as above to enforce the value:
https://docs.amcharts.com/3/javascriptcharts/ValueAxis#strictMinMax
Example of setting minimum inside addInitHandler:
https://codepen.io/team/amcharts/pen/b4be8cb4e3c073909860720e0909a876?editors=1010
If you refresh the data, or use live data, then before you animate or validate the chart to show the updated data, you should recalculate the max and, if you want the axis to change then use chart.valueAxes[0].maximum = max; where max is something you calculate based on data input.
Here is an example:
function loop() {
var data = generateChartData();
chart.valueAxes[0].maximum = max;
// refresh data:
chart.animateData(data, {
duration: 1000,
complete: function () {
setTimeout(loop, 2000);
}
});
}
The lines above were used in this example:
https://codepen.io/team/amcharts/pen/d0d5d03cfdcc2cc256e28ec52ad8b95c/?editors=1010
I am using this library: Dimensional Charting to build some relatively standard charts that need CrossFilter functionality.
I have been following the examples but they aren't working for me.
Here is my code:
var dashData = crossfilter(data.report),
dataByHour = dashData.dimension(function(d){ return d3.time.hour(new Date(d.timestamp))}),
totalByHour = dataByHour.group().reduceSum(function(d) { return d.amount }),
dc.barChart("#graphTimeOverview")
.width(990) // (optional) define chart width, :default = 200
.height(250) // (optional) define chart height, :default = 200
.transitionDuration(500) // (optional) define chart transition duration, :default = 500
.margins({top: 10, right: 50, bottom: 30, left: 40})
.dimension(dataByHour) // set dimension
.group(totalByHour) // set group
.elasticY(true)
.centerBar(true)
.gap(1)
.x(d3.time.scale().domain([new Date(data.report[0].timestamp), new Date(data.report[(data.report.length - 1)].timestamp)]))
.round(d3.time.hour.round)
.xUnits(d3.time.hours)
.renderHorizontalGridLines(true);
dc.renderAll();
I know the crossfilter data is working correctly, here is a sample of the group:
totalByHour:
[ {key:(new Date(1361746800000)), value:6170.17},
{key:(new Date(1361678400000)), value:3003},
{key:(new Date(1361581200000)), value:2350.42},
{key:(new Date(1361667600000)), value:1636.19},
etc...
]
Unfortunately all this gets me is an empty graph, it seems to compute the y-axis correctly, so it would seem to me that it can read the data, however I never see any bar values:
Maybe the data.report array is not sorted by timestamp (the sample provided is unsorted). In your code, you assume that those values are sorted. You can try using
// Compute the timestamp extent
var timeExtent = d3.extent(data.report, function(d) { return d.timestamp; });
dc.barChart("#graphTimeOverview")
// more settings here
.x(d3.time.scale().domain(timeExtent.map(function(d) { return new Date(d); })))
.round(d3.time.hour.round)
.xUnits(d3.time.hours)
.renderHorizontalGridLines(true);
It would be easier to tell what is the problem if you provide a jsFiddle.
I started using dc.js few days ago, so I don't know for sure. But, I think your code should go in
d3.csv("data.csv", function(data) { //your-code };
or
d3.json("data.json", function(data) {//your-code};
or
jQuery.getJson("data.json", function(data){//your-code});