I want to use the eCharts to draw the candletick picture. I get the data from flask using Axios:
request.post('/investor').then(res=>{
var arr=Object.keys(res.close)
for(let i=0;i<arr.length;i++){
this.stockdata[i]=[]
for(let j=0;j<4;j++){
var temp=[]
temp[0]=res.open[i]
temp[1]=res.close[i]
temp[2]=res.low[i]
temp[3]=res.high[i]
this.stockdata[i][j]=temp[j]
}
}
})
this my data:
The 2d array
but it cannot render the website
then i create the data using the method below(assign data manually):
this.stockdata= [
[20, 34, 10, 38],
[40, 35, 30, 50],
[31, 38, 33, 44],
[38, 15, 5, 42]
]
it successfully draw the picture,i don't know why.
and this my main function:
mounted(){
request.post('/investor').then(res=>{
var arr=Object.keys(res.close)
for(let i=0;i<arr.length;i++){
this.stockdata[i]=[]
for(let j=0;j<4;j++){
var temp=[]
temp[0]=res.open[i]
temp[1]=res.close[i]
temp[2]=res.low[i]
temp[3]=res.high[i]
this.stockdata[i][j]=temp[j]
}
}
})
console.log(this.stockdata)
var echarts = require('echarts');
var option = {
xAxis: {
data: ['2017-10-24', '2017-10-25', '2017-10-26', '2017-10-27']
},
yAxis: {},
tooltip: {
trigger: 'axis'
},
series: [
{
type: 'candlestick',
data:this.stockdata
}
]
};
var charts = echarts.init(this.$refs.myChart);
charts.setOption(option);
}
}
There is not enough information, but I can still guess.
I am going to assume that your Object.keys double for loop with i and j works fine. To be sure of that, please add console.log(this.stockdata) after both loops end running, to make sure you are building your data from the response the right way.
The error is probably because in your code you call the async function, which is calling your API.
// this *.then* happens after the initial render, and after a delay it gets a value,
// which is too late
request.post('/investor').then(res=>{
This causes that at the exact frame the App is being rendered, your response is not yet there, causing your App to break because this.stockdata is still undefined. One way to fix it is the following:
// in render function:
{this.stockdata && <YourComponent dataToRender={this.stockdata} />}
This will prevent rendering YourComponent until the API response is there
Related
I'm struggling to get 2 traces on a plotly graph when updating the data from a javascript clientside callback.
I have this clientside callback:
app.clientside_callback(
ClientsideFunction(
namespace='clientside',
function_name='update_rms_graph'
),
Output('rmstrace-graph', 'extendData'),
(
Input('client-rms-data', 'data'),
Input('client-rms-2-data', 'data'),
Input('timer-refresh-rms-fft', 'n_intervals')
)
)
Which is defined as such:
window.dash_clientside = Object.assign({}, window.dash_clientside, {
clientside: {
update_rms_graph: function(data, data_rms, n_intervals) {
console.log(data.rms.length, data_rms.rms.length);
return [
{y: [data.rms], x: [data.t]},
//{y: [data_rms.rms], x: [data_rms.t]}, // This is where i'd like to add my new trace
[0],
data.max_points
]
}
}
)
The 'rmstrace-graph' is defined as such:
html.Div([
dcc.Graph(id='rmstrace-graph', figure=fig_rmstrace),
], className='six columns')
And the corresponding figure used for the dash graph is the following:
fig_rmstrace = go.Figure(data=go.Scattergl(y=[], x=[]))
How should the 'return' from the clientside callback be wrote if I like to get a second trace from the same dash graph ?
return [
{
y: [rms.rms, averaged_rms.rms],
x: [rms.t, averaged_rms.t],
}
[0, 1],
rms.max_points
]
I am struggling with Dash Clientside callbacks. I am looking to create a smooth animation, so I need the clientside callback to have the fast update rate. I have an example that seems to replicate the problem; I have a normal callback and that works as expected. When I convert the same callback to clientside, it no longer works. However, when I do a JSON.stringify to the clientside return, I see the data field updating. I do not understand the issue, though I expect it is an issue with my js. I do not know how to debug on the clientisde, so any advice for error recording would also be appreciated.
Here is the working 'normal' callback:
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input,Output,State
fig_test={
'data': [
{'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
{'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
],
'layout': {
'title': 'Dash Data Visualization'
}
}
app = dash.Dash(__name__)
app.layout = html.Div([
html.Button("Button 1", id="btn1"),
dcc.Graph(id="graph", figure=fig_test),
dcc.Slider(
id='interval-component',
min=0,
max=36,
step=1,
value=10,
),
html.Div(id="log"),
html.Pre(
id='structure',
style={
'border': 'thin lightgrey solid',
'overflowY': 'scroll',
'height': '275px'
}
)
])
#app.callback(
Output("graph", "figure"),
Input('interval-component','value'),Input("graph", "figure"),Input("btn1", "n_clicks"))
def display_structure(value, figure, btn1):
figure['data'][0]['y'][1] = value
return {'data': figure['data'], 'layout':figure['layout']}
app.run_server(debug=False)
Here is the same callback implemented through clientside:
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input,Output,State
fig_test={
'data': [
{'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
{'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
],
'layout': {
'title': 'Dash Data Visualization'
}
}
app = dash.Dash(__name__)
app.layout = html.Div([
html.Button("Button 1", id="btn1"),
dcc.Graph(id="graph", figure=fig_test),
dcc.Slider(
id='interval-component',
min=0,
max=36,
step=1,
value=10,
),
html.Div(id="log"),
html.Pre(
id='structure',
style={
'border': 'thin lightgrey solid',
'overflowY': 'scroll',
'height': '275px'
}
)
])
app.clientside_callback(
"""
function(value, figure, btn1){
figure['data'][0]['y'][1] = value
return {'data': figure['data'], 'layout':figure['layout']};
}
""", Output("graph", "figure"), [Input('interval-component','value'),Input("graph", "figure"),Input("btn1", "n_clicks")])
app.run_server(debug=False)
If I implement the clientside to jsonify the output like this:
app.clientside_callback(
"""
function(value, figure, btn1){
figure['data'][0]['y'][1] = value
return JSON.stringify({'data': figure['data'], 'layout':figure['layout']});
}
""", Output("log", "children"), [Input('interval-component','value'),Input("graph", "figure"),Input("btn1", "n_clicks")])
I can see the value being updated, so I do not know what the issue is.
So I figured out the 'smooth animation' for layout updates, wherein 'extend data' is not possible and so the solution in this answer: Plotly/Dash display real time data in smooth animation
is not applicable. Further it allows smooth animations of live-updated data without being dependent on the 'animate' api. This is not an exact answer to the question I asked, but addresses the concept.
If you are unfamiliar with Frames, or otherwise unsure how to setup a figure, see plotly's example here: https://plotly.com/python/animations/
Psuedocode for setup:
## setup figure,
for data in some_data:
##do something
fig['frames'].append(frame)
fig1 = go.Figure(fig)
setup a store for your frames, and a store for the frames to be passed to a clientside callback. As I was setting up a process to simulate live data acquisition, I had a timer for 'polling' and a secondary one for the animation. If you don't want a timer to as a trigger, the main concept is still the same; have some 'animate' trigger, in this case 'interval-component', to kick off the quickly refreshing secondary timer.
app.layout = html.Div([
dcc.Store(id='frames-stored', data=fig1['frames']),
dcc.Store(id='frames'),
dcc.Interval(
id='interval-component',
interval=1*500, # in milliseconds
n_intervals=0
),
dcc.Interval(id='graph-refresher',
interval=1*25,
n_intervals=0,
max_intervals=50,
disabled=True),
dcc.Graph(id="graph", figure=fig1),
])
now a callback to catch your 'animation' trigger and pass frames to your clientside callback:
#app.callback(
Output("frames", "data"),Output("graph-refresher", "disabled"),
Output("graph-refresher", "max_intervals"),Output('graph-refresher','n_intervals'),
Input("interval-component", "n_intervals"),State("frames-stored", "data"))
def data_smoother(n_intervals,frames):
## can do whatever here as long as a list of frames are passed to the store
selected_frames = frames[n_intervals]
return selected_frames,False,'some_max',0
This callback turns on the timer for the clientside callback, and resets the max_intervals with 'some_max'. This is going to be dependent on whatever you are doing.
Now the clientside callback that handles the 'animation'.
app.clientside_callback(
"""
function(n_intervals, frames){
return {'data':frames[parseInt(n_intervals)]['data'], 'layout':frames[parseInt(n_intervals)]['layout']};
}
""",Output("graph", "figure"),Input('graph-refresher','n_intervals'), State("frames", "data"))
I hope this is useful for someone!
This may be in the series of dumb questions, but when I look at https://deck.gl/docs/api-reference/geo-layers/mvt-layer, I do not understand how to make a MVTLayer that fetches self-hosted tiles without React pieces. Can someone help? This would feel to be even large interest now that buildless is also becoming a thing in web programming.
What I would like to achieve is a simple HTML (e.g. index.html) file that uses a script tag like <script src="https://unpkg.com/deck.gl#8.4.5/dist.min.js"></script> and the example from the aforementioned Deck.gl that looks like (I changed the URL)
import DeckGL from '#deck.gl/react';
import {MVTLayer} from '#deck.gl/geo-layers';
function App({viewState}) {
const layer = new MVTLayer({
data: `https://<selfhostedurl>/{z}/{x}/{y}.pbf`,
minZoom: 0,
maxZoom: 23,
getLineColor: [192, 192, 192],
getFillColor: [140, 170, 180],
getLineWidth: f => {
switch (f.properties.class) {
case 'street':
return 6;
case 'motorway':
return 10;
default:
return 1;
}
},
lineWidthMinPixels: 1
});
return <DeckGL viewState={viewState} layers={[layer]} />;
}
but instead make this a without React. I see it requires a bit more code on how to define a canvas HTML element and use it. Maplibre example would be OK too. :) There is one Maplibre example at https://codepen.io/snickell/pen/dypOWzj.
You can use the Scripting API for more 'simple' examples, here you have an example of using MVTLayer.
Deck.gl offers a standalone bundled version of the library - a native JavaScript scripting interface like that of d3.js.
As simple as
const deckgl = new deck.DeckGL({
container: 'map',
mapStyle: 'https://maps-api-v2.us.carto.com/user/public/carto/sql/{z}/{x}/{y}?source=SELECT * FROM ne_10m_railroads_public&api_key=default_public&format=mvt',
initialViewState: {
latitude: 41.4,
longitude: 2.18,
zoom: 5,
},
controller: true,
layers: [
new deck.MVTLayer({
data: 'https://d25uarhxywzl1j.cloudfront.net/v0.1/{z}/{x}/{y}.mvt',
getLineColor: [192, 192, 192],
lineWidthMinPixels: 1
})
]
});
I’m trying to pass my data to the graph but it won't work. If I add numbers to it manually like
data: [1,2,3]
Than it works. In my code you'll see that I console.logged my data.weight and data.goal. They appear in the console as they should.
Here's the code:
var endpoint = '/api/data';
var defaultDataW = [];
var defaultDataG = [];
$.ajax({
method: 'GET',
url: endpoint,
success: function(data){
console.log(data.weight);
console.log(data.goal);
defaultDataW = data.weight;
defaultDataG = data.goal;
var graph = document.getElementById("weight-graph");
Chart.defaults.global.defaultFontFamily = "Lato";
Chart.defaults.global.defaultFontSize = 18;
var dataFirst = {
label: "Your Weight (Kg)",
data: defaultDataW,
lineTension: 0,
fill: false,
borderColor: 'red'
};
var dataSecond = {
label: "Your Goal (Kg)",
data: defaultDataG,
lineTension: 0,
fill: false,
borderColor: 'blue'
};
var DateData = {
labels: [], //Dates will be here
datasets: [dataFirst, dataSecond]
};
var chartOptions = {
legend: {
display: true,
position: 'top',
labels: {
boxWidth: 80,
fontColor: 'black'
}
}
};
var lineChart = new Chart(graph, {
type: 'line',
data: DateData,
options: chartOptions
});
},
error: function(error_data){
console.log("Error");
console.log(error_data);
}
})
})
You are doing most everything right, but you are changing 'type' of the data.....
what you are doing RIGHT
var defaultDataW = [];
This sets the data to an ARRAY.
Though, in the ajax, you are doing this:
defaultDataW = data.weight;
Which, by your other comments, clearly says that 'data.weight' is an INTEGER.
So, when you try to use that (now INTEGER) as data in a Chart
data: defaultDataW,
It doesn't work (and, no surprise......)
Chart.js requires an ARRAY (at minimum) to work - https://www.chartjs.org/docs/master/general/data-structures/
Now, how can you fix this?????
Pretty simple......
In your ajax, change
defaultDataW = data.weight;
To ADD to the ARRAY (instead of replacing the array with an integer....)
defaultDataW.push(data.weight);
In case you add other code later (or for future projects, putting things in a function, etc....), you might want to also learn about a way to CLEAR that array. It doesn't hurt to do it now, though the way your code is it really should not be needed. I'm including it here for others to reference, etc.
Before doing a 'push' (certainly if you are doing a loop, etc.), you may need to clear the array first, like this:
defaultDataW.length = 0;
This will empty the array and then you can 'push' what you really want in there. If the array is not empty and you push, then you will have the old data as well as the new (of course, depending on what you are doing, that may be a good thing - though in this case, it is not)
In flot, how can I create a pie chart where each wedge is a link to a different web-page?
I gave it a shot, but I wasn't able to do it. I started with this example, then added:
grid: { clickable: true },
right above the "pie: {" line. Then I added a plotclick function at the end:
$("#placeholder").bind("plotclick", function (event, pos, item) {
alert('click!');
for(var i in item){
alert('my '+i+' = '+ item[i]);
}
});
You'll see the "click!" message, but "item" has no properties.
I was thinking you'd just add URLs to the data ojects, then forward the browser to the appropriate URL from within the plotclick function. If you figure it out, I'd be interested to know!
Update: Here's something that might work -- it just turns the labels into links. Put the URLs in your data like this:
$.plot($("#placeholder"), [
{ label: "Serie1", data: 10, url: "http://stackoverflow.com"},
{ label: "Serie2", data: 30, url: "http://serverfault.com"},
{ label: "Serie3", data: 90, url: "http://superuser.com"},
{ label: "Serie4", data: 70, url: "http://www.google.com"},
{ label: "Serie5", data: 80, url: "http://www.oprah.com"},
{ label: "Serie6", data: 110, url: "http://www.realultimatepower.net/"}
],
Then set the labelFormatter to something like:
return ''+serie.label+'<br/>'+Math.round(serie.percent)+'%';
Clicking in the pie wedges themselves still does nothing special, though.
I know this is an old thread but I have discovered another way of doing this.
Make sure grid is set to clickable
var data = [{
"label" : "series1",
"data" : 24,
"url" : "http://stackoverflow.com"
},
{
// etc etc
}];
$.plot($('.chart'), data, function() {
// Your options
grid: {
clickable:true
}
});
Bind a click function to the element and use javascript to redirect to the url.
$('.chart').bind("plotclick", function(event,pos,obj) {
window.location.replace(data[obj.seriesIndex].url);
});
Adding to the answer by Derek Kurth...
It looks like flot is ignoring any additional objects that we include in the JSON. For example when I used
data: [10, 0, "http://stackoverflow.com"]
// 0 is used as an intercept value for y-axis
it worked without any trouble and I was able to access the data from the event handler like
$("#placeholder").bind("plotclick", function (event, pos, item) {
alert(item.series.data);
});
I am new to this flot library and not great at JavaScript. So probably this is not the right way to do things but it works. I have always felt that embedding additional information in UI elements in HTML is a pain :(