I have a site with two vega-lite graphs that show different aspects of the same dataset, which is generated dynamically (similar to this example). Currently they both hold their own version of this dataset.
Since the dataset tends to get quite big I would like them to share the data between them so they use less memory.
Since the data will be updated later (from the function update_vega()) I can't just put it into a variable and embed it in both diagrams.
Is it possible in vega-lite to have multiple graphs sharing the same data-object and how can I do it?
Here is my code (I have only been learning javascript 3 days ago, so I am very happy about feedback on every level):
<!DOCTYPE html>
<html>
<body>
<!-- setup of the vega graph -->
<script src="https://cdn.jsdelivr.net/npm/vega#5.10.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-lite#4.6.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-embed#6.3.2"></script>
<div id="vega_graph_one"></div>
<div id="vega_graph_two"></div>
<script>
var vlSpec_one = {
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
data: { name: 'table', values: [{"time":11, "age": 4},{"time":12, "age": 4},{"time":11, "age": 5}]},
width: 400,
mark: 'bar',
encoding: {
x: {field: 'time', type: 'quantitative', binned: true},
y: {aggregate: "count", type: 'quantitative'},
color: {field: 'age', type: 'quantitative'}
}
}
var vlSpec_two = {
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
data: { name: 'table', values: [{"time":11, "age": 4},{"time":12, "age": 4},{"time":11, "age": 5}]},
width: 400,
mark: 'point',
encoding: {
x: {field: 'time', type: 'quantitative'},
y: {field: 'age', type: 'quantitative'},
}
}
// two global variables so the view can be updated from an outside function
var view_one;
var view_two;
vegaEmbed('#vega_graph_one', vlSpec_one).then(
result => { view_one = result.view;},
reason => { console.error(reason);}
)
vegaEmbed('#vega_graph_two', vlSpec_two).then(
result => { view_two = result.view;},
reason => { console.error(reason);}
)
// update the vega graph from outside
// call with update_vega([{"time":11, "age": 4},{"time":12, "age": 4},{"time":9, "age": 6}])
function update_vega(event_data){
var changeSet = vega.changeset()
.insert(
event_data
);
// data dublication happense here
view_one.change("table", changeSet).run();
view_two.change("table", changeSet).run();
}
</script>
</body>
</html>
First, consider putting your data in javascript function and call it in data, should be easier to maintain for both data and chart:
function getData(){
var mydata = [{"time":11,"age": 4},{"time":12,"age": 4},{"time":11, "age":5}];
return mydata;
}
Then you can call it:
{
"data": getData(),
"mark": {"type" : "bar"}
}
There are several concepts that help achieving data sharing based on your needs.
You need just one vegaEmbed and one vlSpec that utilizes one of the following:
Layers
Put data on top of all layers and refer to it from each mark's layer
{
"data": {"values":getData()},
"layer": [
{
"mark": {"type" : "bar"}
},
{
"mark": {"type" : "point"}
}
]
}
Concat
vconcat, hconcat, and concat can be used to produce multiple charts of different mark each using same data, it doesn't perfectly suite your case because you don't have the same x and y fields in every mark. Read more on Concatenating views
{
"data": {"values":getData()},
"concat": [
{
"mark": {"type" : "line"},
"encoding": {
"x": {"field": "time"},
"y": {"field": "age"}
}
},
{
"mark": {"type" : "bar"},
"encoding": {
"x": {"field": "time"},
"y": {"field": "age"}
}
}
]
}
Related
i want to show below data (sample data from my api) on react highchart line chart. but i am confuse how to prepare this data. the chart should be look alike, at Aaxis all assignments name and at y axis if 4 assignment name then look at data each object have values array. so 4 assignment names means 4 charts with multiple data labels. look at below screen shot. first image is what my chart should look alike. the second screenshot is how my chart giving error data is not loading.i am also attaching codesandbox link. the data is too much i have total 30+ assignments names with array of datalabels but right now i am attaching only 4.
codesandbox link : https://codesandbox.io/s/customizable-react-dashboard-with-chart-fork-forked-15rfpq?file=/src/LineHighchart.js
my sample Data:
const data ={"data":[{"assignment_name":"assignment for oliver","0":26,"1":1,"2":70,"3":80,"4":100,"5":10,"6":0,"7":0},{"assignment_name":"assignment","0":25,"1":0,"2":19,"3":35,"4":310,"5":0,"6":0,"7":0},{"assignment_name":"assignment2","0":27,"1":20,"2":10,"3":30,"4":50,"5":90,"6":0,"7":0},
{"assignment_name":"assignment2","0":29,"1":0,"2":10,"3":0,"4":30,"5":0,"6":60,"7":10},
]}
I am not familiar with the react wrapper for Highcharts, but here is a plain vanilla Highcharts object to show a line graph with two series and five data points, which might help you dig further:
{
"chart": {
"height": 436
},
"xAxis": {
"categories": [
"2019-12-30",
"2020-01-06",
"2020-01-13",
"2020-01-20",
"2020-01-27",
],
"title": {
"text": "Week",
}
},
"series": [{
"name": "Count",
"type": "line",
"data": [ 6, 9, 7, 6, 5 ]
}, {
"name": "Slope",
"type": "spline",
"data": [ 5.9, 6.23, 6.57, 6.9, 7.23 ]
}
]
}
UPDATE:
Here is the answer for your comment question: "can u make array of object from my given data and then give that variable to series":
const data ={
"data": [
{"assignment_name":"assignment for oliver",
"0":26,"1":1,"2":70,"3":80,"4":100,"5":10,"6":0,"7":0},
{"assignment_name":"assignment",
"0":25,"1":0,"2":19,"3":35,"4":310,"5":0,"6":0,"7":0},
{"assignment_name":"assignment2",
"0":27,"1":20,"2":10,"3":30,"4":50,"5":90,"6":0,"7":0},
{"assignment_name":"assignment2",
"0":29,"1":0,"2":10,"3":0,"4":30,"5":0,"6":60,"7":10},
]
};
let categories = Object.keys(data.data[0]).filter(key => key.match(/^\d+$/));
let series = data.data.map(obj => {
return {
name: obj.assignment_name,
tyle: 'line',
data: categories.map(key => obj[key])
}
});
let chartObj = {
"chart": {
"height": 436
},
"xAxis": {
"categories": categories,
"title": {
"text": "Assignments",
}
},
"series": series
};
console.log(chartObj);
I have a dataset containing a date value, and 3 other values. For example:
{
"minimum": "71",
"maximum": "74",
"meanAverage": "72",
"dateTime": "2018-03-28T13:46:00"
},
{
"minimum": "57",
"maximum": "87",
"meanAverage": "71",
"dateTime": "2018-03-28T18:00:01"
},
I'd like to create a chart using react highcharts with the dates in the x axis, and then 3 lines on the y axis showing the corresponding 3 values for all the dates.
So far I thought using 3 different arrays to pass to series.data. Each array would have the x value (date) and then the y value. For the minimuns, it would be:
['2018-03-28T13:46:00', '71']
['2018-03-28T18:00:01', '57']
...
And in the options object, I would have something like:
series: [
{
data: minimuns
},
{
data: maximuns
},
{
data: meanAverages
},
]
But this isn't working and I'm having some trouble finding the info to correctly do this. Any help?
You need to parse the json object into a suitable data format.
See the docs for accepted data formats:
https://api.highcharts.com/highcharts/series.line.data
Notice, that y value should be a number.
The example config:
const json = '[{"minimum": 71, "maximum": 74, "meanAverage": 72, "dateTime": "2018-03-28T13:46:00"}, {"minimum": 57,"maximum": 87, "meanAverage": 71, "dateTime": "2018-03-28T18:00:01"}]',
parsed = JSON.parse(json);
const minimum = [];
parsed.forEach(data => {
minimum.push([new Date(data.dateTime).getTime(), data.minimum])
})
const maximum = [];
parsed.forEach(data => {
maximum.push([new Date(data.dateTime).getTime(), data.maximum])
})
const meanAverage = [];
parsed.forEach(data => {
meanAverage.push([new Date(data.dateTime).getTime(), data.meanAverage])
})
Then you can use your data arrays as you expected:
series: [{
data: minimum
},
{
data: maximum
},
{
data: meanAverage
}
]
Demo:
https://jsfiddle.net/BlackLabel/2ncb83eh/
I am generating some dynamic vizframe column charts by looping at the response I get from my Odata service. One of my requirement for the chart is to show columns in different color depending on the value of a field I have in data. Let's call it validation status.
I have a fairly good idea on how to achieve this in normal situations by using the setVizProperties method and setting up rules for the dataPointStyle properties. It would still have been possible if I had the same criteria for all the charts and all the values. But that isn't the case since I need to check each record individually to determine it's status. So I thought of using the callback functionality of the dataPointStyle. But the problem here is that although it gives me the context but it doesn't tell me from which chart this callback has been triggered. My idea is that if I get the chart name or it's reference then I can access it's model and determine the color.
So if I can somehow get a reference of the vizframe from where the call back is being triggered, it will solve my problem.
callback
Description:
function (data, extData) {...} => true|false
A function to determine whether a given data matches the rule. Parameters:
data is an object with all bound field ids as keys, and corresponding values as values. It helps to consider it as the object containing everything you see in datapoint mouseover tooltip. If unbound dimensions or measures are set in FlatTableDataset context field, the related key/value pairs will be included in this parameter as well.
extData is an object with all other measure fields that in the same line with current data point. Measure Ids as keys, and corresponding values as values. It helps to compare value between different measures.
Link to the vizFrame documentation
JSFiddle
My Data looks something like this:
[{
"RunLogId": "0000000040",
"RuleId": "00016",
"CreatedOn": "2020-07-21",
"CreatedAt": "09:44:35",
"NAV_SUBSCRIBED_LOGS": {
"results": [
{
"RunLogId": "0000000040",
"Sequence": "00001",
"RuleId": "00016",
"Variation": "-3.94",
"ValidationStatus": "F",
"Dimension": "ABC"
},
{
"RunLogId": "0000000040",
"Sequence": "00002",
"RuleId": "00016",
"Variation": "1.04",
"ValidationStatus": "S",
"Dimension": "DEF"
}
]
}
},
{
"RunLogId": "0000000033",
"RuleId": "00014",
"CreatedOn": "2020-07-15",
"CreatedAt": "11:10:09",
"NAV_SUBSCRIBED_LOGS": {
"results": [
{
"RunLogId": "0000000033",
"Sequence": "00001",
"RuleId": "00014",
"Variation": "-2.36",
"ValidationStatus": "F",
"Dimension": "ABC"
},
{
"RunLogId": "0000000033",
"Sequence": "00002",
"RuleId": "00014",
"Variation": "-5.05",
"ValidationStatus": "F",
"Dimension": "DEF"
}
]
}
}]
My code looks some like this :
for (var i = 0; i < chartsCount; i++) {
var oModel = new JSONModel();
var chartData = aSubscriptions[i].NAV_SUBSCRIBED_LOGS.results;
var aDimensions = [];
var aDimFeeds = [];
aDimensions.push({
name: "Dimension",
value: "{Dimension}"
});
aDimFeeds.push("Dimension");
oModel.setData(chartData);
oModel.refresh();
var oDataset = new FlattenedDataset({
dimensions: aDimensions,
measures: [{
name: "Variation",
value: "{Variation}"
}],
data: {
path: "/"
}
});
var oVizFrame = new VizFrame();
oVizFrame.setVizType("column");
oVizFrame.setHeight("450px");
oVizFrame.setDataset(oDataset);
oVizFrame.setModel(oModel);
var feedValueAxisActual = new sap.viz.ui5.controls.common.feeds.FeedItem({
"uid": "valueAxis",
"type": "Measure",
"values": ["Variation"]
}),
feedCategoryAxis = new sap.viz.ui5.controls.common.feeds.FeedItem({
"uid": "categoryAxis",
"type": "Dimension",
"values": aDimFeeds
});
oVizFrame.addFeed(feedValueAxisActual);
oVizFrame.addFeed(feedCategoryAxis);
oVizFrame.setVizProperties({
plotArea: {
dataPointStyle: {
"rules": [
{
callback: function (oContext, extData) {
that.checkValue(oContext, "S");
},
"properties": {
"color": "sapUiChartPaletteSemanticGoodLight1"
},
"displayName": "Successful"
}
, {
callback: function (oContext, extData) {
that.checkValue(oContext, "F");
},
properties: {
color: "sapUiChartPaletteSemanticBadLight1"
},
"displayName": "Failed"
}
],
others: {
properties: {
color: "sapUiChartPaletteSemanticNeutral"
},
"displayName": "Undefined"
}
}
}
});
//Chart Container
var oChartContainer = new ChartContainer();
var oChartContainerContent = new ChartContainerContent();
oChartContainerContent.setContent(oVizFrame);
oChartContainer.addContent(oChartContainerContent);
}
Not sure if I understand you correctly but I'll give it a shot anyway. If I was wrong let me know.
You create charts in a loop. You want to access the specific chart in a callback.
Why don't you access oVizFrame in your callback?
First I would replace the for loop with a forEach. forEach calls a given function for every element in your array:
aSubscriptions.forEach(function(oSubscription) {
const oModel = new JSONModel();
const chartData = oSubscription.NAV_SUBSCRIBED_LOGS.results;
...
}
In a for loop your variables are reused. In a forEach function a new scope is created for every item. So when you access oVizFrame in your callback it is the same oVizFrame that you declared earlier.
Then you should be able to access oVizFrame in your callback.
oVizFrame.setVizProperties({
plotArea: {
dataPointStyle: {
"rules": [{
callback: function(oContext, extData) {
// >>>>>>>> Do something with oVizFrame <<<<<<<<
that.checkValue(oContext, "S");
},
...
}, {
callback: function(oContext, extData) {
// >>>>>>>> Do something with oVizFrame <<<<<<<<
that.checkValue(oContext, "F");
},
...
}],
...
}
}
});
I'm using the Google Chart Tools Directive Module to draw a line/area chart in my Angularjs application, from rdf data retrieved via sparql queries and available, within the app, in json-like format.
In the main controller I declared my drawing function like this:
$scope.createChart = function () {
var json1 = $scope.entities // here I have my data
var rows = []
// populate array with data:
for (var key in json1) {
if (json1[key]['qb:dataset'] == $scope.dsUri) {
var date = new Date(json1[key]['sdmx-dimension:refTime']);
var deads = json1[key]['dpc:deads'];
var newpos = json1[key]['dpc:newPositive'];
var intcare = json1[key]['dpc:intensiveCare'];
rows.push({ c: [ { v:date }, { v:deads }, { v:newpos }, { v:intcare} ] });
}
}
// sort rows by dates
rows.sort(function (rowA, rowB) {
return rowA.c[0].v.getTime() - rowB.c[0].v.getTime();
});
// define chart object
$scope.myChartObject = {
"type": "LineChart",
"data": {
"cols": [
{
"id": "date",
"label": "Date",
"type": "date"
},
{
"id": "deaths",
"label": "Deaths",
"type": "number"
},
{
"id": "newpos",
"label": "New Positive",
"type": "number"
},
{
"id": "intCare",
"label": "Intensive Care",
"type": "number"
}
]
},
"options": {
"title": "Observations",
"height": 400,
"legend": { position: 'bottom' },
"width": 'auto'
}
}
// add rows to data
$scope.myChartObject.data.rows = rows;
return $scope.myChartObject;
}
}]);
And in my HTML I got my chart div:
<div google-chart chart="createChart()" class="mychartClass"></div>
Now the problem with this solution is that the chart gets blank drawn first and - if query doesn't take much time - filled later.
How to wait for data to be retrieved from queries before drawing the chart?
I've tried setting a timeout but this is not the best way to go.
It is better to have the chart directive follow a scope variable:
̶<̶d̶i̶v̶ ̶g̶o̶o̶g̶l̶e̶-̶c̶h̶a̶r̶t̶ ̶c̶h̶a̶r̶t̶=̶"̶c̶r̶e̶a̶t̶e̶C̶h̶a̶r̶t̶(̶)̶"̶ ̶c̶l̶a̶s̶s̶=̶"̶m̶y̶c̶h̶a̶r̶t̶C̶l̶a̶s̶s̶"̶>̶<̶/̶d̶i̶v̶>̶
<div google-chart chart="myChart" class="mychartClass"></div>
Using a function causes the AngularJS framework to invoke the function every digest cycle. This leads to doing alot of unnecessary computation.
Instead do the computation after the data arrives from the server.
$http.get(url).then(function(response) {
$scope.entities = response.data;
$scope.myChart = $scope.createChart();
});
This way the chart is only computed once when the data arrives from the server.
I'm looking at this template to build a web application: https://js.devexpress.com/Demos/WidgetsGallery/Demo/PivotGrid/FieldChooser/AngularJS/Light/
In the example there are static data. I have to retrieve them from the server. So, I wrote this:
$scope.testData = [];
$scope.pivotGridDataSource = new DevExpress.data.PivotGridDataSource({
fields: [{
caption: "Nome",
dataField: "fullName",
area: "row"
}, {
caption: "Country",
dataField: "country",
area: "column"
}, {
caption: "Count",
dataField: "countOne",
dataType: "number",
summaryType: "sum",
area: "data"
}],
store: $scope.testData
});
$scope.pivotGridOptions = {
allowSortingBySummary: true,
allowSorting: true,
allowFiltering: true,
showBorders: true,
dataSource: $scope.pivotGridDataSource,
fieldChooser: {
enabled: false
}
},
$scope.fieldChooserOptions = {
dataSource: $scope.pivotGridDataSource,
texts: {
allFields: "All",
columnFields: "Columns",
dataFields: "Data",
rowFields: "Rows",
filterFields: "Filter"
},
width: 400,
height: 400,
bindingOptions: {
layout: "layout"
}
};
// Now I call the server to retrieve data
$scope.getTestData = () => {
$scope.testData.length = 0;
result.forEach(e => {
$scope.testData.push(e);
);
$scope.pivotGridDataSource.reload();
}
$scope.getTestData();
The problem is that when the data are loaded, in the Fields below it shows just the fields written at the beginning (so the name, the count and the country). But I saw in the demo that it should be display ALL parameters of the object.
For example, if the object is so structured:
{ "name": "Test1", "country": "Germany", "creationDate": "xxx", "surname": "yyy" }
So, I expect that in the fields there should be ALL parameters, so name, country, creationDate, surname. So, I did this at the beginning:
I changed $scope.testData = [] into:
$scope.testData = [{ "name": "", "country": "", "creationDate": "", "surname": "" }]
so the component will preparare all fields. And this works. But what if the server gives me back an Object that has another parameters? How can I display them?
I tried so after the calling and before the reload():
let fields = $scope.pivotGridDataSource.fields();
let newField = {
llowExpandAll: false,
allowFiltering: true,
allowSorting: true,
allowSortingBySummary: true,
caption: "This is a new field",
dataField: "newField",
dataType: "string",
displayFolder: "",
index: fields.length
}
$scope.pivotGridDataSource.fields().push(newField);
$scope.pivotGridDataSource.reload();
But it doesn't work yet. Worse, it does not even initialize the Pivot.
The fieldChooser uses the store fields, in this case $scope.testData fields, in your code I see your store is first declared (as null or with some format as you described) and then you have a function to fill it.
I don't know how your code looks and why you create your store that way, but that is basically your problem (the flow).
In the sample code the flow is:
Store with data (static in this case)
PivotGridDataSource
FieldChooser
In your code the flow is:
Store (empty)
PivotGridDataSource
FieldChooser
Store (fill) --> at this point your FieldChooser has been initialized with the fields of the empty version of your store so not much to do (in Jquery you could re-create your object, you dan do it using Jquery within AngularJs see a simple code sample here and below)
$('#chartContainer').dxChart('instance').dispose();
$('#chartContainer').dxPieChart({
...
});
To avoid all of this you can just use the DevExpress.data.CustomStore and your flow will be basically identical to the demo.