I need the x-axis values, when hovering over my plotly chart.
According to the plotly docs (https://plot.ly/javascript/hover-events/) the hover event callback should contain the field "points" from which I should get the x value.
But if you look at this basic example you can see that the callback does not contain the field "points". Also other fields like "data" are undefined:
HTML
<div id="tester" style="width:600px;height:250px;"></div>
JS
$(document).ready(function(){
var tester = $('#tester');
tester.on('plotly_hover', function(data){
console.log(data)
});
Plotly.plot( 'tester', [{
x: [1, 2, 3, 4, 5],
y: [1, 2, 4, 8, 16] }], {
margin: { t: 0 } } );
})
See this fiddle in order to try it yourself:
https://jsfiddle.net/c1kt3r82/158/
What am I doing wrong?
plotly-basic does not seem to support hover events, use plotly-latest instead
when using jQuery to select the element, it returns a different object than doing it via document.getElementById
the hover events need to be defined after calling plot
$(document).ready(function() {
var tester = document.getElementById('tester');
Plotly.plot(tester, [{
x: [1, 2, 3, 4, 5],
y: [1, 2, 4, 8, 16]
}], {
margin: {
t: 0
}
});
tester.on('plotly_hover', function(data) {
console.log(data)
});
});
<div id="tester" style="width:600px;height:250px;"></div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
Related
I got some trouble understanding the way .data works in D3.js, even by reading and re-reading the documentation. Let's imagine the following code:
const chart = selection => {
const svg = selection.selectAll('svg').data(d => d);
console.log(svg.data());
}
d3.select('#root').data([
[{
name: 'Foo',
values: [1, 2, 3]
},
{
name: 'Bar',
values: [4, 5, 6]
},
]
]).call(chart);
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="root">
</div>
I would expect to get the array with Foo and Bar objects to be logged in this code. Instead, I get an empty array.
According to documentation:
The data is specified for each group in the selection. If the
selection has multiple groups (such as d3.selectAll followed by
selection.selectAll), then data should typically be specified as a
function. This function will be evaluated for each group in order,
being passed the group’s parent datum
I probably misunderstand something here. Can an expert enlighten me? :)
Related JSFiddle here: https://jsfiddle.net/tmq4h8w2/
What you have here...
const svg = selection.selectAll('svg').data(d => d);
... is what we call an "update" selection in a D3 code. What it does is:
Select all <svg> elements;
Binds the data to them;
Since you don't have any <svg> element here, your update selection is empty. Check the console in the demo below, which uses your code exactly as it is:
const chart = selection => {
const svg = selection.selectAll('svg').data(d => d);
console.log("The size of the update selection is: " + svg.size());
}
d3.select('#root').data([
[{
name: 'Foo',
values: [1, 2, 3]
},
{
name: 'Bar',
values: [4, 5, 6]
},
]
]).call(chart);
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="root">
</div>
That's why, for appending elements, we don't need to select anything:
selection.selectAll(null)//see the 'null' here
.data(data)
.enter()
.append("svg");
On the other hand, if you already had <svg> elements in your HTML, your "update" selection wouldn't be empty: it would bind the data to the selected elements and you could clearly log the bound data. The demo below just adds two SVG elements in the HTML, your code is the same:
const chart = selection => {
const svg = selection.selectAll('svg').data(d => d);
console.log(svg.data());
}
d3.select('#root').data([
[{
name: 'Foo',
values: [1, 2, 3]
},
{
name: 'Bar',
values: [4, 5, 6]
},
]
]).call(chart);
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="root">
<svg></svg>
<svg></svg>
</div>
The issue is that your selection doesn't bind that data to svg on its own. You need to enter the data join (and append the object that uses the data) so that it properly comes into being:
const chart = selection => {
const svg = selection.selectAll('svg').data(d => d)
.enter()
.append('svg');
console.log(svg.data());
}
d3.select('#root').data([[
{ name: 'Foo', values: [1, 2, 3] },
{ name: 'Bar', values: [4, 5, 6] },
]]).call(chart);
Fiddle here: https://jsfiddle.net/dfzc0ub7/
After you call it into being, you should be able to select and update without the need for enter().
I am trying to highlight a single set of values in a c3.js stacked bar chart. Following this example I can change the color of a single bar in a non-stacked bar, but I can't figure out how to identify the indexes of a single stack.
My JS is:
var chart = c3.generate({
bindto: '#chart1',
data: {
x: 'x',
columns: [
['x', "1ST", "2ND", "3RD", "4TH"],
['A', 6, 8, 2, 9],
['B', 3, 4, 1, 6],
['C', 4, 4, 2, 4]
],
type: 'bar',
groups: [
['A', 'B', 'C']
],
colors: {
A: '#666666',
B: '#cccccc',
C: '#428bca',
},
order: 'asc'
},
axis: {
x: {
type: 'category'
}
}
});
And the approach of the example is to do:
color: function (color, f) {
return f.index === 4 ? "#dd0" : "#ddd";
}
Work in progress JSFIDDLE.
How can I get the indexes of the 3 values of a single stack and then use them to change their colors? For example, if I want to highlight "2ND" by changing the colours to a set of reds?
Adapted from the example here -->
http://c3js.org/samples/data_color.html
color : function (color, d) {
return d.index && d.index === 2 ? "#dd0" : color;
},
https://jsfiddle.net/kmjpg30c/3/
d.index gives you the index of the stack - however, in your example there is no index 4 as the indexing starts at 0, so you have [0,1,2,3] as a possible range
So test against the index and return a new color if it matches and if it doesn't return the color that is originally passed in.
(the d.index test is present as the color function gets used by other routines that pass in just a string indicating the dataset ("A", "B", "C" in your example), so you need to test for the existence of the fields you need in d first.
if you want to highlight just one part of the stack use the .id field as well e.g. d.index === 2 && d.id === "A"
I am trying to generate multiple c3 charts with the following code :
var datas=[[
["name", "position", "y", "bigRect", "myBars"],
["One 22", 2, 2, 2, 2],
["One 33", 3, 3, 2, 2],
["One 44", 4, 4, 2, 2]
],[
["name", "position", "y", "bigRect", "myBars"],
["Two 55", 5, 5, 2, 2],
["Two 66", 6, 6, 2, 2],
["Two 77", 7, 7, 2, 2]
],[
["name", "position", "y", "bigRect", "myBars"],
["Three 88", 8, 8, 2, 2],
["Three 99", 9, 9, 2, 2],
["Three 00", 0, 0, 2, 2]
]];
var iData = 0;
var charts = [];
for(iData in datas){
var d = datas[iData];
document.querySelector(".container").innerHTML += "<div id='chart"+iData+"'></div>";
var chartSelector = "#chart"+iData;
charts[iData] = c3.generate({
bindto: d3.select(chartSelector),
data: {
rows: d,
type: "scatter",
types: {
bigRect: "area",
myBars: "bar"
},
x: "position",
y: "y"
},
zoom: {
enabled: true
}
});
}
All the charts look empty except the last one that works perfectly. You can see what it looks like on this JSbin link.
On the hidden charts, all the SVGs are generated, but
the g SVG elements that contain the path drawing the dots and bars are set on opacity: 0, hiding all their contents.
the zoom and the tooltip do not work either
Do you know why c3 is disabling the first charts and how to enable them ?
My apologies for my poor English and thank you very much for your time.
You've got it working now, but I can also get it working by replacing one line like so:
d3.select(".container").append("div").attr("id", "chart"+iData);
//document.querySelector(".container").innerHTML += "<div id='chart"+iData+"'></div>";
It appears adding stuff to and then replacing .innerHtml has side effects for the existing contents, in your case the first charts you build
Is it possible to append to innerHTML without destroying descendants' event listeners?
This includes wiping out event handlers and 'unofficial' (for want of a better term) attributes like the __data__ field d3 populates and uses (it's undefined for the first 2 sets of bars as this code will reveal)
for(iData in datas){
console.log (d3.select("#chart"+iData+" .c3-bars").node().__data__);
}
I finally solved my problem by creating all the #chartX containers in another loop before that calling c3.
I assume it has something to do with the non-procedural execution order of JS but I'm still looking for a precise explanation of the phenomenon.
Here is the link to the corrected JSbin.
Please forgive me if this is a dumb or basic question but I have not been able to find a good solution. I have a json array of numbers:
[30, 37,34,56,76,87,54,34,2,4,2,5,5,3,4,3,4, 90]
I would like to count how many times each number occurs and use that data to produce a graph using d3js. How can I go about doing this? If there is a D3 method that does this, that would be great. But a javascript/jquery solution would do as well.
In plain Javascript:
var items = [30, 37, 34, 56, 76, 87, 54, 34, 2, 4, 2, 5, 5, 3, 4, 3, 4, 90],
histogram = items.reduce(function (r, a) {
r[a] = (r[a] || 0) + 1;
return r;
}, {});
document.write('<pre>' + JSON.stringify(histogram, 0, 4) + '</pre>');
For the graphing, check out c3. This can be easily done with something like this:
var chart = c3.generate({
data: {
x: 'x',
columns: [
numbers.unshift('x'),
occurrences.unshift('occurrences'),
],
type: 'bar'
} });
where numbers is an array of all distinct numbers and occurrences is an array of the numbers of time each occurs.
Demo
I'm making a request for x and y values from a flax/python backend using Polymer and I can read the values in console for the XMLHttpResquest response, but now I need to convert the output into a set of discrete x and y values (so it can be read by C3.js - a framework sitting on top D3 for graphing).
Here's the code I'm using to get the XMLHttpResquest response:
<paper-button affirmative hover on-tap="{{addNewGraph}}">Submit</paper-button>
Polymer("add-graphItem",{
addNewGraph: function () {
var HeaderName = this.$.graphOptionsLoad.$.headerValue.selectedItem.label;
var FunctionName = this.$.graphFunctionsLoad.$.functionValue.selectedItem.label;
console.log("The options are " +HeaderName +" and " +FunctionName);
var params = {};
if (this.$.graphOptionsLoad.$.headerValue.selectedItem) {
params['DataHeader'] = this.$.graphOptionsLoad.$.headerValue.selectedItem.label;
}
if (this.$.graphFunctionsLoad.$.functionValue.selectedItem) {
params['FunctionName'] = this.$.graphFunctionsLoad.$.functionValue.selectedItem.label;
}
this.$.sendOptions.params = JSON.stringify(params);
var x = this.$.sendOptions.go();
// this.$.sendOptions.go();
console.log(x)
// var ajax = document.querySelector("sendOptions");
var results = [];
this.addEventListener("core-response",
function(e) {
console.log(x.response);
}
);
}
});
And here's an example of the output from console.log(x.response);:
{
"graph": [
{
"Header": "MakeModeChange"
},
{
"x": [
0.0,
131.35,
26971.3,
27044.75,
27351.4,
27404.483333333334,
27419.416666666668,
33128.96666666667,
33549.13333333333,
34049.48333333333,
77464.26666666666,
77609.71666666666,
174171.85,
259166.98333333334
]
},
{
"y": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14
]
}
]
}
Ultimately I need the output to look something like:
['x', 0.0, 131.35, 26971.3, 27044.75, 27351.4, 27404.483333333334...],
['y', 1, 2, 3, 4, 5, 6]
Here's a quick and dirty way to do it - not recommended for large scale or if you don't fully trust the response values. Also, obvious, but if their api/data structures changes you're SOL.
xArr = JSON.parse(x.response).graph[1].x
xArr.unshift('x')
yArr = JSON.parse(y.response).graph[2].y
yArr.unshift('y')
You'll get both arrays you'll need, you can combine as needed