Dynamically Toggle Visibility of Shapes in Plotly.js - javascript

I am creating an array of plotly.js shapes which work great. I have added a custom data attribute of 'custType' : '1' or 'custType' : '2' based on the type of shape. The number of each shape type will not be consistent so I need to be able to update the visibility dynamically.
I have tried this... to no avail
var update = [];
for(x=0; x<data.layout.shapes.length; x++){
if(data.layout.shapes[x].custType == '1'){
update.push({'shapes[' + x + '].visible':false})
}
}
Plotly.relayout('main', update);

update has to be an object instead of an array, like
{
"shapes[0].visible": false,
"shapes[1].visible": false
}
See the snippet below.
const trace = {
x: [1, 2, 3, 4],
y: [10, 15, 13, 17],
type: 'scatter'
};
const data = [trace];
const layout = {
shapes: [
{
type: 'rect',
x0: 1,
x1: 2,
y0: 10,
y1: 12,
fillcolor: '#d3d3d3',
line: {
width: 0
}
},
{
type: 'rect',
x0: 3,
x1: 4,
y0: 12,
y1: 14,
fillcolor: '#d3d3d3',
line: {
width: 0
}
}
]
}
Plotly.newPlot('myDiv', data, layout);
function update() {
const update = {};
for(let i = 0; i < layout.shapes.length; i++){
update['shapes[' + i + '].visible'] = false;
}
Plotly.relayout('myDiv', update);
}
<head>
<!-- Load plotly.js into the DOM -->
<script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
</head>
<body>
<div id='myDiv'><!-- Plotly chart will be drawn inside this DIV --></div>
<button type="button" onClick="update()">Update</button>
</body>

Related

Add and visualize custom values assigned to nodes of plotly

This is a follow-up to my previous question posted here How to add custom values to nodes of plotly graph.
The solution posted works well for selecting a single node using plotly_click. As suggested in the comments, I tried to use plotly_selected for displaying multiple nodes.
I am trying to select multiple nodes and display the values associated with the nodes using the following code.
<html>
<head>
<script src="https://cdn.plot.ly/plotly-1.58.5.min.js"></script>
<style>
.graph-container {
display: flex;
justify-content: center;
align-items: center;
}
.main-panel {
width: 70%;
float: left;
}
.side-panel {
width: 30%;
background-color: lightgray;
min-height: 300px;
overflow: auto;
float: right;
}
</style>
</head>
<body>
<div class="graph-container">
<div id="myDiv" class="main-panel"></div>
<div id="lineGraph" class="side-panel"></div>
</div>
<script>
var nodes = [
{ x: 0, y: 0, z: 0, value: [1, 2, 3] },
{ x: 1, y: 1, z: 1, value: [4, 5, 6] },
{ x: 2, y: 0, z: 2, value: [7, 8, 9] },
{ x: 3, y: 1, z: 3, value: [10, 11, 12] },
{ x: 4, y: 0, z: 4, value: [13, 14, 15] }
];
var edges = [
{ source: 0, target: 1 },
{ source: 1, target: 2 },
{ source: 2, target: 3 },
{ source: 3, target: 4 }
];
var x = [];
var y = [];
var z = [];
for (var i = 0; i < nodes.length; i++) {
x.push(nodes[i].x);
y.push(nodes[i].y);
z.push(nodes[i].z);
}
var xS = [];
var yS = [];
var zS = [];
var xT = [];
var yT = [];
var zT = [];
for (var i = 0; i < edges.length; i++) {
xS.push(nodes[edges[i].source].x);
yS.push(nodes[edges[i].source].y);
zS.push(nodes[edges[i].source].z);
xT.push(nodes[edges[i].target].x);
yT.push(nodes[edges[i].target].y);
zT.push(nodes[edges[i].target].z);
}
var traceNodes = {
x: x, y: y, z: z,
mode: 'markers',
marker: { size: 12, color: 'red' },
type: 'scatter3d',
text: [0, 1, 2, 3, 4],
hoverinfo: 'text',
hoverlabel: {
bgcolor: 'white'
},
customdata: nodes.map(function(node) {
if (node.value !== undefined)
return node.value;
}),
type: 'scatter3d'
};
var layout = {
margin: { l: 0, r: 0, b: 0, t: 0 }
};
Plotly.newPlot('myDiv', [traceNodes], layout, { displayModeBar: false });
// max y value for the line plot
const ymax = Math.max(...nodes.map(n => n.value).flat());
document.getElementById('myDiv').on('plotly_selected', function(data){
var selectedNodeIndices = data.points.map(function(point) {
return point.pointNumber;
});
var values = [];
for (var i = 0; i < selectedNodeIndices.length; i++) {
values.push(nodes[selectedNodeIndices[i]].value);
}
var data = [];
for (var i = 0; i < values.length; i++) {
data.push({
type: 'scatter',
mode: 'lines',
x: [0, 1, 2],
y: values[i],
name: 'Node ' + selectedNodeIndices[i]
});
}
Plotly.newPlot('lineGraph', data, {
margin: { t: 0 },
yaxis: {autorange: false, range: [0, ymax + 1]}
});
});
</script>
</body>
</html>
There is some issue, I am not able to select multiple nodes and the line graph is not plotted when nodes are clicked.
Suggestions on how to fix this will be really helpful.
EDIT:
<html>
<head>
<script src="https://cdn.plot.ly/plotly-1.58.5.min.js"></script>
<style>
.graph-container {
display: flex;
justify-content: center;
align-items: center;
}
.main-panel {
width: 70%;
float: left;
}
.side-panel {
width: 30%;
background-color: lightgray;
min-height: 300px;
overflow: auto;
float: right;
}
</style>
</head>
<body>
<div class="graph-container">
<div id="myDiv" class="main-panel"></div>
<div id="lineGraph" class="side-panel"></div>
</div>
<script>
var nodes = [
{ x: 0, y: 0, z: 0, value: [1, 2, 3] },
{ x: 1, y: 1, z: 1, value: [4, 5, 6] },
{ x: 2, y: 0, z: 2, value: [7, 8, 9] },
{ x: 3, y: 1, z: 3, value: [10, 11, 12] },
{ x: 4, y: 0, z: 4, value: [13, 14, 15] }
];
var edges = [
{ source: 0, target: 1 },
{ source: 1, target: 2 },
{ source: 2, target: 3 },
{ source: 3, target: 4 }
];
var x = [];
var y = [];
var z = [];
for (var i = 0; i < nodes.length; i++) {
x.push(nodes[i].x);
y.push(nodes[i].y);
z.push(nodes[i].z);
}
const edge_x = [];
const edge_y = [];
const edge_z = [];
for (var i = 0; i < edges.length; i++) {
const a = nodes[edges[i].source];
const b = nodes[edges[i].target];
edge_x.push(a.x, b.x, null);
edge_y.push(a.y, b.y, null);
edge_z.push(a.z, b.z, null);
}
var traceNodes = {
x: x, y: y, z: z,
mode: 'markers',
marker: { size: 12, color: Array.from({length: nodes.length}, () => 'red') },
text: [0, 1, 2, 3, 4],
hoverinfo: 'text',
hoverlabel: {
bgcolor: 'white'
},
customdata: nodes.map(function(node) {
if (node.value !== undefined)
return node.value;
}),
type: 'scatter3d'
};
var traceEdges = {
x: edge_x,
y: edge_y,
z: edge_z,
type: 'scatter3d',
mode: 'lines',
line: { color: 'red', width: 2},
opacity: 0.8
};
var layout = {
margin: { l: 0, r: 0, b: 0, t: 0 }
};
Plotly.newPlot('myDiv', [traceNodes, traceEdges], layout, { displayModeBar: false });
// max y value for the line plot
const ymax = Math.max(...nodes.map(n => n.value).flat());
document.getElementById('myDiv').on('plotly_click', function(data){
var nodeIndex = data.points[0].pointNumber;
var values = nodes[nodeIndex].value;
// Change color of the selected node
traceNodes.marker.color = Array.from({length: nodes.length}, (_, i) => i === nodeIndex ? 'blue' : 'red');
Plotly.update('myDiv', {
marker: {
color: traceNodes.marker.color
}
}, [0]);
Plotly.newPlot('lineGraph', [{
type: 'scatter',
mode: 'lines',
x: [0, 1, 2],
y: values
}], {
margin: { t: 0 },
yaxis: {autorange: false, range: [0, ymax + 1]}
});
});
</script>
</body>
</html>
I tried to define the selected marker's color i.e. blue to indicate that it has been selected. Unfortunately, this is not working.
Regarding selecting multiple nodes,
Currently, one single curve is visible on the side panel. I would like to know how to select multiple nodes and display multiple curves.
Unfortunately selection features don't work with 3d scatter plot : lasso and select tools are not available and the event plotly_selected won't fire. See this issue for more info.
That said, it is still possible to handle multi-point selection manually using the plotly_click event with some extra code. The idea is to use a flag and keypress/keyup events to determine whether a given point should be added to or removed from the current selection, or if it should be added to a new selection (ie. the flag is on when holding the shift or ctrl key).
The second thing is to define a selection object that can persist across different click events, which means we need to define it outside the handler.
(only the end of the script changes)
// max y value for the line plot
const ymax = Math.max(...nodes.map(n => n.value).flat());
// Accumulation flag : true when user holds shift key, false otherwise.
let accumulate = false;
document.addEventListener('keydown', event => {
if (event.key === 'Shift') accumulate = true;
});
document.addEventListener('keyup', event => {
if (event.key === 'Shift') accumulate = false;
});
// Selected points {<nodeIndex> : <nodeData>}
let selection = {};
document.getElementById('myDiv').on('plotly_click', function(data){
if (data.points[0].curveNumber !== 0)
return;
const nodeIndex = data.points[0].pointNumber;
if (accumulate === false)
selection = {[nodeIndex]: data.points[0]};
else if (nodeIndex in selection)
delete selection[nodeIndex];
else
selection[nodeIndex] = data.points[0];
// Highlight selected nodes (timeout is set to prevent infinite recursion bug).
setTimeout(() => {
Plotly.restyle('myDiv', {
marker: {
size: 12,
color: nodes.map((_, i) => i in selection ? 'blue' : 'red')
}
});
}, 150);
// Create a line trace for each selected node.
const lineData = [];
for (const i in selection) {
lineData.push({
type: 'scatter',
mode: 'lines',
x: [0, 1, 2],
y: selection[i].customdata,
});
}
Plotly.react('lineGraph', lineData, {
margin: {t: 0},
yaxis: {autorange: false, range: [0, ymax + 1]},
});
});

Is there a way to get the "customdata" property of a clicked point in Plotly?

As far as I understood the documentation of Plotly the clickEvent can only hand over the coordinates of the nearest point to the cursor position. But is there any way to get the clicked point's properties like "text" or in my case the "customdata"?
Edit:
I am not even sure, if I used the customdata property right:
var trace = {
x: [5],
y: [7],
name: 'example',
hovertemplate: '%{text}',
text: ['example'],
mode: 'markers',
visible: 'legendonly',
marker: {
size: [257],
sizeref: 2,
sizemode: 'area',
opacity: 0.3,
customdata: ['https://google.com/']
}
};
customdata needs to be assigned to your trace data, not the marker object.
The plotly_click event returns the points with all its attributes. You can then access customdata and text for each clicked point.
The example below is based on https://plotly.com/javascript/click-events/#binding-to-click-events
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
var myPlot = document.getElementById('myDiv'),
d3 = Plotly.d3,
N = 26,
x = d3.range(N),
y = d3.range(N).map( d3.random.normal() ),
customdata = d3.range(N).map(x => characters[x]),
text = d3.range(N).map(x => characters[x] + '-' + characters[x])
data = [ {
x: x,
y: y,
type: 'scatter',
customdata: customdata,
text: text,
mode: 'markers',
marker: {size: 16}
} ],
layout = {
hovermode: 'closest',
title: 'Click on Points',
hovertemplate: '%{text}'
};
Plotly.newPlot('myDiv', data, layout);
myPlot.on('plotly_click', function(data){
var msg = 'Closest point clicked:';
for(var i=0; i < data.points.length; i++){
msg += '\ncustomdata = ' + data.points[i].customdata;
msg += '\ntext = ' + data.points[i].text;
}
alert(msg);
});
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<div id="myDiv" style:width: 100%></div>
</body>

Echart: How to set mark area to fill sections in xAxis

I have a problem with marking area: i need to be able to select a bar area based on xAxis, for example from 0 to 1, from 1 to 2, etc. But when i try to provide options for bar like
[{xAxis: 0, itemStyle: {color: red}},{xAxis: 1}]
it marks an area from a middle of xAxis area with an index of 0 to a middle of xAxis area with an index of 1. Is there a way to make it mark from start of an area to an end. Currently i managed to do so only with x option in pixels:
https://codesandbox.io/s/react-echart-markarea-ksj31?file=/src/index.js:714-726
Is there a better way to do it?
I can't imagine a method that would cover your requirements. It seems there is no such but nothing prevents to do it ourselves, see below.
When call function with join = true markedArea will calc as range from first to last.
calcMarkAreaByBarIndex(myChart, join = true, [4, 9])
When call function with join = false markedArea will calc for each bar.
calcMarkAreaByBarIndex(myChart, join = true, [4, 5, 6, 9])
var myChart = echarts.init(document.getElementById('main'));
var option = {
tooltip: {},
xAxis: {
data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
},
yAxis: {},
series: [
{
id: 'myBar',
name: 'Series',
type: 'bar',
data: [11, 11, 11, 11, 12, 13, 110, 123, 113, 134, 93, 109],
markArea: {
data: [
[{x: 184},{x: 216}],
[{x: 224},{x: 256}],
]
},
},
]
};
myChart.setOption(option);
function calcMarkAreaByBarIndex(chartInstance, join = false, barIdx){
var series = chartInstance.getModel().getSeriesByType('bar');
var seriesData = series.map((s, idx) => s.getData())[0];
var barNum = seriesData.count();
var barCoors = [];
var layout = idx => seriesData.getItemLayout(idx);
for(var i = 0; i < barNum; i++){
if(!barIdx.includes(i)) continue;
barCoors.push([
{ x: layout(i).x },
{ x: layout(i).x + layout(i).width },
])
}
if(join){
return [
[
{ x: barCoors[0][0].x },
{ x: barCoors[barCoors.length - 1][1].x }
]
]
} else {
return barCoors
}
}
var markedAreas = {
series: {
id: 'myBar',
markArea: {
data: calcMarkAreaByBarIndex(myChart, join = true, [4,9])
}
}
};
myChart.setOption(markedAreas);
<script src="https://cdn.jsdelivr.net/npm/echarts#4.7.0/dist/echarts.min.js"></script>
<div id="main" style="width: 600px;height:400px;"></div>
I found a solution, that worked for me:
Basically, you need to manually set yAxis's max props, add another xAxis, make it invisible, create a custom series with type 'bar' and set xAxisIndex to 1:
data: [maxYaxisValue,maxYaxisValue...], //length === xAxis.data.length
type: 'bar',
barWidth: '100%',
color: transparent,
xAxisIndex: 1,
And style a bar by index with background color and borderWidth
You can check the working example here
https://codesandbox.io/s/react-echart-markarea-m0mgq?file=/src/index.js

Best way of create/delete/restyle graph dynamically with Plotly.js?

I want to add and delete graphs with buttons on my page.
I have to pass layout, and data as Json to the plotly.plot() function.
How can i do this dynamically?
Example code from reference :
var trace1 = {
x: [1, 2, 3, 4],
y: [10, 15, 13, 17],
type: 'scatter'
};
var trace2 = {
x: [1, 2, 3, 4],
y: [16, 5, 11, 9],
type: 'scatter'
};
var data = [trace1, trace2];
var layout = {
width: 500,
height: 500
};
Plotly.newPlot('myDiv', data,layout);
i receive my data via ajax from a database.
function getTrace_data(x,y) {
$.ajax({
type: "GET",
url: "get.php?action=data&x="+x+"&y="+y
dataType: "json",
success: function(data){
drawGraph(data);
},
error: function(error){
console.log(error);
}
});
}
function drawGraph(data)
{
var trace1 = {
x: data.x,
y: data.y,
type: 'scatter'
};
var layout = {
width: 500,
height: 500
};
Plotly.newPlot('myDiv', data,layout);
}
Now i can draw a graph, but how should i change the type of the graph dynamically? or layout options?
You can just overwrite the existing graph with a new one and dynamically change the layout of your graph with a few variables, see the snippet below. Just assume the buttons are different AJAX calls.
function changeGraph(graphType) {
var traces = [];
var graph_types = [];
var myDiv = document.getElementById("mydiv");
switch (graphType) {
case 1:
graph_types.push("scatter");
graph_types.push("bar");
break;
case 2:
graph_types.push("bar");
graph_types.push("bar");
break;
default:
graph_types.push("scatter");
graph_types.push("scatter");
}
traces.push({
x: [1, 2, 3, 4],
y: [10, 15, 13, 17],
type: graph_types[0]
});
traces.push({
x: [1, 2, 3, 4],
y: [16, 5, 11, 9],
type: graph_types[1]
});
var layout = {
width: 500,
height: 500
};
Plotly.newPlot(myDiv, traces, layout);
}
document.getElementById("button0").addEventListener("click", function () {
changeGraph(0);
});
document.getElementById("button1").addEventListener("click", function () {
changeGraph(1);
});
document.getElementById("button2").addEventListener("click", function () {
changeGraph(2);
});
document.getElementById("button0").click();
<script src=https://cdn.plot.ly/plotly-latest.min.js></script>
<div id="mydiv"></div>
<button id="button0">Scatter only</button>
<button id="button1">Bar&Scatter</button>
<button id="button2">Bars only</button>

Test alignment 'Center' to the highchart legend content

I have been working on the highchart for a while, I have the chart like this
and i have placed the legend at the botton of the page and what i need to do is make the legend text align center like in the following image
I found few question of text alignment in highchart but does not suite my request. So I am unable to move further.
Fiddle http://jsfiddle.net/AbNpB/12/
Thanks in Advance!
It's possible to align each line using a custom code that will translate each legend item.
It's easier to resize output window in JSFiddle: http://jsfiddle.net/BlackLabel/a02czac2/
$(function() {
(function(H) {
H.wrap(H.Legend.prototype, 'render', function(proceed) {
// proceed
proceed.apply(this, [].slice.call(arguments, 1));
// custom
var legend = this,
centerRow = function(tab, w, x) {
var offset = (legend.legendWidth - (x + w)) / 2;
H.each(tab, function(elem) {
elem.legendGroup.attr({
translateX: elem.legendGroup.translateX + offset
});
});
}
if (legend.options.centerItemLines) {
var items = legend.allItems || [],
lastY = items[0] && items[0]._legendItemPos[1],
rowItems = [],
pos, prevX, prevW;
H.each(items, function(item) {
pos = item._legendItemPos;
if (pos[1] > lastY) {
lastY = pos[1];
centerRow(rowItems, prevW, prevX);
rowItems = [];
}
rowItems.push(item);
prevX = pos[0];
prevW = item.legendGroup.getBBox(true).width;
});
centerRow(rowItems, prevW, prevX); // last line
}
});
}(Highcharts))
var chart = new Highcharts.Chart({
chart: {
renderTo: 'container'
},
legend: {
itemStyle: {
width: 350
},
centerItemLines: true
},
series: [{
data: [6, 4, 2],
name: 'First'
}, {
data: [7, 3, 2],
name: 'Second a longer legend text and longer and longer and longer'
}, {
data: [9, 4, 8],
name: 'Third'
}, {
data: [1, 2, 6],
name: 'Fourth'
}, {
data: [4, 6, 4],
name: 'Fifth'
}]
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="container" style="height: 400px;"></div>
Try to add this one:
legend: {
align: 'center',
verticalAlign: 'bottom',
x: 0,
y: 0
},
Hope this helps.

Categories

Resources