I have looked at various documentation and similar questions on here, but cannot seem to find the particular solution. Apologies if I have missed anything obvious or have repeated this question!
As a bit of background info, I have implemented a graph using the Chart.js plugin and I am trying to pass the required data from a database.
The arrays of data are the following:
loggedIn: [6.3, 2.4, 7.6, 5.4, 9.9, 7.8],
available: [6.7, 2.2, 11.2, 5.5, 10.1, 7.9],
availableForExisting: [7.2, 3.1, 8.2, 5.6, 9.2, 10.2],
My problem is that only one of the line graphs is being update whilst the rest aren't. The full Chart.JS iFrame code is the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.bundle.min.js"></script>
</head>
<body onLoad="ready()">
<canvas id="myChart" width="250" height="200"></canvas>
<script>
var ctx = document.getElementById("myChart");
const loggedIn = [26, 36, 42, 38, 40, 30, 12];
const available = [34, 44, 33, 24, 25, 28, 25];
const availableForExisting = [16, 13, 25, 33, 40, 33, 45];
const years = [1, 2, 3, 4, 5];
var myChart = new Chart(ctx,
{
type: 'line',
data:
{
labels: years,
datasets: [
{
label: 'Start Balance',
data: loggedIn,//[], //start empty
borderColor:
[
'rgba(164,126,44,1.000)'
],
borderWidth: 1
},
{
label: 'Interest',
data: available,//[], //start empty
borderColor:
[
'rgba(5,99,59,1.000)'
],
borderWidth: 1
},
{
label: 'End Balance',
data: availableForExisting,//[], //start empty
borderColor:
[
'rgba(255,148,112,1.000)'
],
borderWidth: 1
}
]
},
options:
{
tooltips:
{
callbacks:
{
label: function(tooltipItem, data)
{
const title = data.labels[tooltipItem.index];
const dataset = data.datasets[tooltipItem.datasetIndex];
const value = dataset.data[tooltipItem.index];
return title + ': ' + Number(value).toFixed(2) + "%";
}
},
},
onClick: handleClick
}
});
window.onmessage = function(event)
{
if (event.data && Array.isArray(event.data))
{
myChart.data.datasets[0].data = event.data[0];
myChart.data.datasets[1].data = event.data[1];
myChart.data.datasets[2].data = event.data[2];
myChart.update();
}
else
{
console.log("HTML Code Element received a generic message:");
console.log(event.data);
}
};
function handleClick(e)
{
var activeBars = myChart.getElementAtEvent(e);
var value = myChart.config.data.datasets[activeBars[0]._datasetIndex].data[activeBars[0]._index];
var label = activeBars[0]._model.label;
window.parent.postMessage(
{
"type": "click",
"label": label,
"value": value
}, "*");
}
function ready()
{
window.parent.postMessage(
{
"type": "ready"
}, "*");
}
</script>
</body>
</html>
I need to display the data in multiple line graphs, however only one is being updated. The way I am passing the data from the frontend is as follows:
let data =
{
loggedIn: [6.3, 2.4, 7.6, 5.4, 9.9, 7.8],
available: [6.7, 2.2, 11.2, 5.5, 10.1, 7.9],
availableForExisting: [7.2, 3.1, 8.2, 5.6, 9.2, 10.2],
};
$w("#html4").postMessage(data);
$w("#html4").onMessage((event) =>
{
if (event.data.type === 'ready')
{
$w("#html4").postMessage(days[year]);
}
});
Related
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
I'm attempting to integrate ZingChart as a custom component type in GrapesJs. I've followed some examples and have implemented the following plugin.
blocks.js
import { lineChartRef, chartType } from './consts';
export default (editor, opt = {}) => {
const c = opt;
const bm = editor.BlockManager;
if (c.blocks.indexOf(lineChartRef) >= 0) {
bm.add(lineChartRef, {
label: c.labelLineChart,
content: `
<div data-gjs-type="${chartType}" id="myChart"></div>
`
});
}
};
components.js
import { chartType } from './consts';
export default (editor, opt = {}) => {
const domc = editor.DomComponents;
const defaultType = domc.getType('default');
const defaultModel = defaultType.model;
domc.addType(chartType, {
model: defaultModel.extend(
{
defaults: {
...defaultModel.prototype.defaults,
script: function() {
if (typeof zingchart == 'undefined') {
var script = document.createElement('script');
script.src =
'https://cdn.zingchart.com/zingchart.min.js';
document.body.appendChild(script);
}
}
}
},
{
isComponent: el => {
if (
el.getAttribute &&
el.getAttribute('data-gjs-type') === chartType
) {
return {
type: chartType
};
}
}
}
),
view: {
onRender() {
renderZingChart.bind(this)();
}
}
});
function renderZingChart() {
const data = {
type: 'bar',
title: {
text: 'Data Basics',
fontSize: 24
},
legend: {
draggable: true
},
scaleX: {
// Set scale label
label: { text: 'Days' },
// Convert text on scale indices
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
scaleY: {
label: { text: 'Temperature (°F)' }
},
plot: {
animation: {
effect: 'ANIMATION_EXPAND_BOTTOM',
method: 'ANIMATION_STRONG_EASE_OUT',
sequence: 'ANIMATION_BY_NODE',
speed: 275
}
},
series: [
{
// plot 1 values, linear data
values: [23, 20, 27, 29, 25, 17, 15],
text: 'Week 1'
},
{
// plot 2 values, linear data
values: [35, 42, 33, 49, 35, 47, 35],
text: 'Week 2'
},
{
// plot 2 values, linear data
values: [15, 22, 13, 33, 44, 27, 31],
text: 'Week 3'
}
]
};
const chart = {
id: 'myChart',
data
};
zingchart.render(chart);
}
};
index.js
import grapesjs from 'grapesjs';
import loadBlocks from './blocks';
import loadComponents from './components';
import { lineChartRef } from './consts';
export default grapesjs.plugins.add('fndy-charts', (editor, opts = {}) => {
let c = opts;
let defaults = {
blocks: [lineChartRef],
defaultStyle: 1,
labelLineChart: 'Line Chart'
};
// Load defaults
for (let name in defaults) {
if (!(name in c)) c[name] = defaults[name];
}
loadBlocks(editor, c);
loadComponents(editor, c);
});
consts.js
export const lineChartRef = 'line-chart';
export const chartType = 'chart';
When I add the block to the canvas, it renders, but the ZingChart inside does not. Some things I've tried already:
Verify that the ZingChart render function is being called.
Try moving the renderZingChart function call to different component hooks. Specifically, component:mount, view.init(), and view.onRender().
Move the renderZingChart function call to the script function as a script.onload callback. A similar example can be found here: https://grapesjs.com/docs/modules/Components-js.html#basic-scripts. This does render the ZingChart correctly but doesn't feel correct, and does not allow me to pass in parameters since the script function runs outside the scope of GrapesJs.
I'm running out of ideas so any guidance would be great! Thanks!
I'm making a chart component library with echarts, and the approach for rendering the chart would be similar. The only missing thing I see is element's id. It is an attribute that zing uses to render the chart.
I've made a small example which is obviously not production ready because the id of the block is static. This solves specifically the render problem to make the id dynamic you can do it listening to component:add event and add model id as attribute.
const plugin = editor => {
const {
BlockManager: bm
} = editor;
bm.add("mychart", {
label: "Chart",
content: {
tagName: "div",
attributes: {
id: 'myChart'
},
style: {
width: "300px",
height: "300px"
},
script: function() {
const init = () => {
const data = {
type: "bar",
title: {
text: "Data Basics",
fontSize: 24
},
legend: {
draggable: true
},
scaleX: {
// Set scale label
label: {
text: "Days"
},
// Convert text on scale indices
labels: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
scaleY: {
label: {
text: "Temperature (°F)"
}
},
plot: {
animation: {
effect: "ANIMATION_EXPAND_BOTTOM",
method: "ANIMATION_STRONG_EASE_OUT",
sequence: "ANIMATION_BY_NODE",
speed: 275
}
},
series: [{
// plot 1 values, linear data
values: [23, 20, 27, 29, 25, 17, 15],
text: "Week 1"
},
{
// plot 2 values, linear data
values: [35, 42, 33, 49, 35, 47, 35],
text: "Week 2"
},
{
// plot 2 values, linear data
values: [15, 22, 13, 33, 44, 27, 31],
text: "Week 3"
}
]
};
const chart = {
id: this.id,
data
};
zingchart.render(chart);
};
if (typeof zingchart == "undefined") {
var script = document.createElement("script");
script.onload = init;
script.src = "https://cdn.zingchart.com/zingchart.min.js";
document.body.appendChild(script);
} else {
init();
}
}
}
});
};
const editor = grapesjs.init({
container: "#gjs",
fromElement: true,
height: "100vh",
width: "auto",
storageManager: false,
panels: {
defaults: []
},
plugins: ["gjs-preset-webpage", plugin]
});
You can give a check here the chart is rendering.
Codepen
Hope that's enough, cheers!
I don't think you need to write very complicated code for using Zing charts.I will add a small sample code for making a chart block element , So when you drag and drop the block element then it will make the chart a part of the gjs div of grapesjs .I am using Highcharts.
editor.BlockManager.add('Blockhighcharts', {
label: 'Highchart',
category: 'CHART',
attributes: { class:'gjs-fonts gjs-f-b1' },
content: {
script: function () {
var container = "container"+Math.floor(Math.random() * 100);
$(this).attr("id",container);
$('#gridly_div').append($(this));
var myChart = Highcharts.chart(container, {
chart: {
type: 'bar',
},
title: {
text: 'Fruit Consumption'
},
xAxis: {
categories: ['Apples', 'Bananas', 'Oranges']
},
yAxis: {
title: {
text: 'Fruit eaten'
}
},
series: [{
name: 'Jane',
data: [1, 0, 4]
}, {
name: 'John',
data: [5, 7, 3]
}]
});
The HTML code where the chart will be displayed is as follows.
<div id="gjs" style="height:0px; overflow:hidden;">
<style>
#gjs{
height: 100%;
width: 100%;
margin: 0;
}
</style>
<div id='gridly_div' class="gridly">
</div>
i'm using c3js library to render line chart, where I want to group x-axis category labels, as asked in this questions: c3 js: How can I group by Year on the X-axis labels?.
I tried above solution but it is only working only for series type 'timeseries' not for 'category'.
somewhat, I have find the solution using jQuery. following is code:
var xAxis = ['x', "G1 - Team_1", "G2 - Team_1", "G3 - Team_1", "G4 - Team_1", "G1 - Team_2", "G2 - Team_2", "G3 - Team_2", "G4 - Team_2", "G1 - Team_3", "G2 - Team_3", "G3 - Team_3", "G4 - Team_3", "G1 - Team_4", "G2 - Team_4", "G3 - Team_4", "G4 - Team_4", "G1 - Team_5", "G2 - Team_5", "G3 - Team_5", "G4 - Team_5"],
match1 = ['match1', 32, 4, 2, 46, 24, 54, 18, 65, 87, 25, 3, 6, 16, 63, 46, 62, 69, 37, 50, 65],
match2 = ['match2', 68, 60, 95, 65, 59, 67, 56, 69, 38, 74, 59, 83, 53, 72, 16, 12, 64, 93, 51, 93];
var chart = c3.generate({
data: {
x: 'x',
columns: [xAxis, match1, match2],
type: 'spline',
},
line: {
connectNull: true,
},
axis: {
x: {
label: {
text: 'Group(s)'
},
type: 'category',
tick: {
multiline: true,
centered: true
},
height: 50
}
}
});
//this function changes x-axis labels
function changeXAxisLabel() {
var ticks = $('.c3-axis.c3-axis-x').find('.tick');
var ticksData = ticks.map(function(x, ele) {
return {
text: ele.textContent,
ele: ele,
name: ele.textContent.replace('G1', '')
.replace('G2', '')
.replace('G3', '')
.replace('G4', '')
//.replace('-', '').trim()
};
}).get();
var groupedData = {};
ticksData.forEach(function(ele) {
if (!groupedData[ele.name]) groupedData[ele.name] = [];
groupedData[ele.name].push(ele);
});
Object.keys(groupedData).forEach(function(k) {
if (groupedData[k].length < 2) return;
var addOn = Math.ceil(groupedData[k].length / 2);
var translates = [];
groupedData[k].forEach(function(tick, index) {
var $tick = $(tick.ele);
var val = $tick.attr('transform').replace(/[translate,(),]/g, '').split(' ')[0];
translates.push(Number(val));
$tick.find('tspan:eq(0)').text(tick.text.replace(new RegExp(tick.name, 'i'), '').trim());
$tick.find('tspan:not(:eq(0))').remove();
if (index == addOn) {
var cloned = $tick.clone();
//cloned = cloned.find('line').remove();
var pos = (translates[index] + translates[index - 1]) / 2;
cloned.attr('transform', 'translate(' + pos + ',20)');
cloned.find('tspan').text(tick.name);
cloned.insertAfter($tick);
$(cloned).find('line').remove();
}
})
});
}
at first it is displaying desired result:
But after window resize, x-axis labels reverted as it seems chart reinitialize. I tried it with calling changeXAxisLabel on window.resize callback:
$(window).resize(function() {
changeXAxisLabel()
})
But its not running as expected. and it throws an error:
Error: <g> attribute transform: Expected number, "translate(NaN, 0)".
Now I didn't understand, how this can be fixed.
How can I remove this error? or there is other method to solve this problem?
EDIT:
Here is jsfiddle for this code sample: https://jsfiddle.net/abhinaw/1m6v8mwh/
Thanks
Instead of doing in $(window).resize() can you try calling your function as follow :
var chart = c3.generate({
// just add this function, keep remaining code as it is.
onrendered: function () {
changeRoundChartXAxisLabel();
}
});
here is updated version of your JSFiddel.
Removed following code
$(window).resize(function() {
changeRoundChartXAxisLabel()
})
setTimeout(changeRoundChartXAxisLabel, 100);
however i am not able to fix following error
Error: <g> attribute transform: Expected number, "translate(NaN, 0)".
The issue is shown below.
c2chart1 and c2chart1p are identical graph and shares same data. Issue is c2chart1 is getting updated, but not c2chart1p for the second time.
$('#update').bind('click', function() {
c2updateLineGraph(2, [
[0, 105993],
[25, 659727],
[50, 648727],
[75, 636627],
[100, 636627]
]);
c2updateLineGraph(3, [
[0, 115993],
[25, 659727],
[50, 648727],
[75, 336627],
[100, 236627]
]);
setTimeout(function(){
c2updateLineGraph(2, [
[0, 5993],
[25, 659727],
[50, 648727],
[75, 636627],
[100, 63667]
]);
c2updateLineGraph(3, [
[0, 125993],
[25, 259727],
[50, 648727],
[75, 536627],
[100, 236627]
]);
}, 8000);
});
var c2graphdata = [{
name: 'Current year',
data: []
}, {
name: 'Reapair v1',
data: []
}, {
name: 'Repair v2',
data: []
}, {
name: 'Replacement v1',
data: []
}, {
name: 'Replacement v2',
data: []
}, {
name: 'Facelift v1',
data: []
}, {
name: 'Facelift v2',
data: []
}, {
name: 'Reconstruction v1',
data: []
}, {
name: 'Reconstruction v2',
data: []
}];
function c2updateLineGraph(index, data) {
c2chart1.series[index].setData(data, true);
c2chart1p.series[index].setData(data, true);
}
var c2chart1 = Highcharts.chart('container1', {
series: c2graphdata
});
var c2chart1p = Highcharts.chart('container2', {
series: c2graphdata
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="container1"></div>
<div id="container2"></div>
<button id="update">Update charts</button>
The problem is using data variable twice in setData() method. Highcharts use this variable as a reference (library doesn't copy this array). Solution is simple, use data.slice():
function c2updateLineGraph(index, data) {
c2chart1.series[index].setData(data.slice(), true);
c2chart1p.series[index].setData(data.slice(), true);
}
Working demo: http://jsfiddle.net/BlackLabel/hhh2zx3w/1/
Well, I made fiddle based on your code.
See this. :)
HighChart updated
I don't know how it works but, both init functions have to separate.
function chart1Update(index, data) {
c2chart1.series[index].setData(data, true);
}
function chart1pUpdate(index, data){
c2chart1p.series[index].setData(data, true);
}
Can someone give me a simple example of a AnnotatedTimeLine visualization? All the examples I can find have only one line, despite the docs talking about multiple lines.
Adn if you're feeling particularly kind, you might give me an example of what a (python) datasource schema looks like for the example.
Thanks in advance.
I can answer this myself now:
<html>
<head>
<title>Home</title>
<script src="http://www.google.com/jsapi?YOUR_KEY"></script>
<script type="text/javascript">
google.load("jquery", "1");
google.load("jqueryui", "1")
google.load('visualization', '1', {'packages':['annotatedtimeline']});
google.setOnLoadCallback(init);;
function draw_timeline() {
var url = "http://YOUR_GOOGLE_DATA_SOURCE_CALL";
var query = new google.visualization.Query(url);
query.send(callback_draw_timeline);
function callback_draw_timeline(response) {
if (response.isError()) {
alert("Error in query: " + response.getMessage());
return;
};
var data = response.getDataTable();
var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('timeline'));
chart.draw(data, {title: 'Title'} );
};
};
function init() {
draw_timeline();
};
</script>
<link rel="stylesheet" href="main.css" type="text/css">
</head>
<body>
<div id="timeline" style='height: 600px; width=100%'>Timeline</div>
</body></html>
Then in the data source :
schema = { 'time': ("datetime", "Time"),
'col1': ("number", 'Column_1'),
'col2': ("number", 'Column_2'),
'col3': ("number", 'Column_3') }
data = [ { 'time': datetime(2009, 11, 24, 12, 31, 0), 'col1': 23, 'col2': 25, 'col3': 20 },
{ 'time': datetime(2009, 11, 24, 12, 32, 0), 'col1': 31, 'col2': 22, 'col3': 22 },
{ 'time': datetime(2009, 11, 24, 12, 33, 0), 'col1': 21, 'col2': 32, 'col3': 22 },
]
data_table = gviz_api.DataTable(schema)
data_table.LoadData(data)
response.data += data_table.ToJSonResponse(columns_order=(order))
return response