I'm using a QML BarSeries to display some data and encountered a problem: the y-axis of the BarSeries doesn't update automatically.
Below is an mcve copied and modified from the BarSeries docs. It updates the bar series when the user clicks on the background.
// main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import QtCharts 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ChartView {
id: chartView
title: "Bar series"
anchors.fill: parent
legend.alignment: Qt.AlignBottom
antialiasing: true
BarSeries {
id: mySeries
axisX: BarCategoryAxis { categories: ["2007", "2008", "2009", "2010", "2011", "2012" ] }
BarSet { label: "Bob"; values: [2, 2, 3, 4, 5, 6] }
BarSet { label: "Susan"; values: [5, 1, 2, 4, 1, 7] }
BarSet { label: "James"; values: [3, 5, 8, 13, 5, 8] }
}
}
MouseArea {
anchors.fill: parent
onClicked: {
mySeries.clear(); // clear previous sets
// update with new sets
mySeries.append("Bob", [3, 5, 8, 13, 5, 8]);
mySeries.append("Susan", [2, 2, 3, 4, 5, 200]);
mySeries.append("James", [5, 1, 2, 4, 1, 7]);
}
}
}
From the code, we could see that the click on the mouse area should update the series to have a y-axis of up to 200 (due to Susan's new set of values).
The screenshots below show the columns updating but not the y-axis. (Note that I'm expecting the y-axis maximum to update to 200.)
Before the mouse click:
After the mouse click:
What changes should I make to update the maximum of the chart's y-axis?
After the multiple mySeries.append statements in MouseArea::onClicked, I tried doing chartView.update() but this worked to no avail.
I searched and researched but found nothing. Most answers from the web concern only QtCharts run from C++ or describe a different issue (unless I searched with the wrong keywords?).
For completeness, here's the main.cpp file:
#include <QApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv); // needs QT += widgets in qmake
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
I was able to solve this issue by attaching a custom ValueAxis to the BarSeries and manually, programmatically updating the new maximum with the ValueAxis::max property.
import QtQuick 2.6
import QtQuick.Window 2.2
import QtCharts 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ChartView {
id: chartView
title: "Bar series"
anchors.fill: parent
legend.alignment: Qt.AlignBottom
antialiasing: true
BarSeries {
id: mySeries
axisX: BarCategoryAxis { categories: ["2007", "2008", "2009", "2010", "2011", "2012" ] }
axisY: ValueAxis { // <- custom ValueAxis attached to the y-axis
id: valueAxis
}
BarSet { label: "Bob"; values: [2, 2, 3, 4, 5, 6] }
BarSet { label: "Susan"; values: [5, 1, 2, 4, 1, 7] }
BarSet { label: "James"; values: [3, 5, 8, 13, 5, 8] }
}
}
MouseArea {
anchors.fill: parent
onClicked: {
mySeries.clear();
mySeries.append("Bob", [3, 5, 8, 13, 5, 8]);
mySeries.append("Susan", [2, 2, 3, 4, 5, 200]);
mySeries.append("James", [5, 1, 2, 4, 1, 7]);
valueAxis.max = 200; // hard-code a new maximum
}
}
}
This works splendidly. Here's what the chart now looks like after a click on the background:
Here's a solution that dynamically calculates the new maximum (only the onClicked slot is shown, for brevity):
onClicked: {
mySeries.clear();
mySeries.append("Bob", [3, 5, 8, 13, 5, 8]);
mySeries.append("Susan", [2, 2, 3, 4, 5, 200]);
mySeries.append("James", [5, 1, 2, 4, 1, 7]);
// deduce the new min and max
var min = 1e8, max = -1e8;
for (var i = 0; i < mySeries.count; i++) {
// min = Math.min(min, ...mySeries.at(i).values); // modern js not yet supported?
// max = Math.min(max, ...mySeries.at(i).values);
min = Math.min(min, mySeries.at(i).values.reduce(function(a,b) {
return Math.min(a, b);
}));
max = Math.max(max, mySeries.at(i).values.reduce(function(a,b) {
return Math.max(a, b);
}));
}
// set the new min and max
valueAxis.min = min;
valueAxis.max = max;
// valueAxis.max = max * 1.05; // or give a little margin?
}
Of course, the minimum could be left out of the picture, but that entirely depends on your data and situation.
Related
Can anyone tell me why some items from a dropdown list are not visible when running the jar application? It seems to happen randomly. I create an ObservableList, then recursively create a Line, assign it a stroke type from the observable list through a String as line.setStyle("-fx-stroke-dash-array:" + dashString + ";"). Finally, everything works fine and the ComboBox gets the whole list of line and a certain selection. But it sometimes fail to display lines in the dropdown list. The code is:
package test;
import java.util.ArrayList;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class ComboLines extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
ComboBox<Line> dashCombo = new ComboBox<>();
initDashLineCombo(dashCombo, dashUnit, 2);
dashCombo.getStylesheets().add("test/style.css");
dashCombo.setStyle("-fx-min-width: 100; -fx-max-width: 100; -fx-pref-width: 100; -fx-min-height: 15; -fx-pref-height: 15; -fx-max-height: 15;");
StackPane root = new StackPane();
root.getChildren().add(dashCombo);
primaryStage.setScene(new Scene(root, 150, 300));
primaryStage.show();
}
public static void initDashLineCombo(ComboBox<Line> cb, ObservableList<int[]> list, int i)
{
ArrayList<Line> LinesList = new ArrayList<>(); // to store lines
// cycle over the list and generate a line with dashing defined by list
for(int j = 0; j < list.size(); j++)
{
int sum = 0;
int[] dash = list.get(j);
String dashString = " ";
for(int k : dash) // cycle over an int array which defines the dashing
{ dashString += k + " ";
sum += k; }
System.out.println("sum = " + sum);
Line line = new Line(15, 0, 1*(15+sum), 0);
System.out.print("\ndashString :" + dashString);
// apply style
line.setStrokeWidth(2);// line width
line.setStroke(Color.BLACK);
line.setStyle("-fx-stroke-dash-array:" + dashString + ";"); // line dashing
LinesList.add(line); // collect the line
}
// create the observable list
final ObservableList<Line> LinesObs = FXCollections.observableArrayList(LinesList);
// set all line items in the combo box
cb.setItems(LinesObs);
// select an item ion the combo box
cb.setValue(LinesObs.get(i-1));
}
// ... dashing ...
int[] s10 = {1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3,};
int[] s9 = {1,3,1,3,1,4,5,4,5,4, 1,3,1,3,1,4,5,4,5,4};
int[] s8 = {6,3,6,3,6,5,1,5, 6,3,6,3,6,5,1,5};
int[] s7 = {1,3,1,3,1,6,6,6, 1,3,1,3,1,6,6,6};
int[] s6 = {5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3};
int[] s5 = {5, 4, 5, 4, 1, 3, 1, 4, 5, 4, 5, 4, 1, 3, 1, 4};
int[] s4 = {5, 4, 1, 3, 1, 4, 5, 4, 1, 3, 1, 4, 5, 4, 1, 3, 1, 4};
int[] s3 = {5, 3, 1, 3, 5, 3, 1, 3, 5, 3, 1, 3, 5, 3, 1, 3};
int[] s2 = {15, 3, 15, 3, 15, 3};
int[] s1 = {60};
final ObservableList<int[]> dashUnit = FXCollections.observableArrayList(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10);
}
/* style.css */
.combo-box {
-fx-min-width: 35;
-fx-pref-width: 35;
-fx-max-width: 35;
-fx-min-height: 20;
-fx-pref-height: 20;
-fx-max-height: 20;
-fx-padding: -2 -8 -2 -5;
}
.combo-box-base > .arrow-button {
-fx-background-radius: 0 3 3 0, 0 2 2 0, 0 1 1 0;
-fx-background-color: transparent;
-fx-alignment: center-left;
}
.combo-box .cell {
-fx-font-size: 8.75pt;
-fx-font-family: "Arial";
-fx-text-fill: BLACK;
-fx-alignment: BASELINE_RIGHT;
-fx-padding: 0 3 0 -5;
}
invisible lines
UPDATE: Using cell factory a simplified version (no CSS and no dashing applied) is like this. The same problem persists and further I cannot get the initial selection applied.
package test;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ComboLines extends Application
{
public static void main(String[] args) { launch(args); }
ComboBox<Line> dashCombo = new ComboBox<>();
#Override
public void start(Stage primaryStage)
{
StackPane root = new StackPane();
root.getChildren().add(dashCombo);
dashCombo.setItems(FXCollections.observableList(DASHUNIT));
dashCombo.getSelectionModel().selectFirst();
dashCombo.setCellFactory(new Callback<ListView<Line>, ListCell<Line>>()
{
#Override
public ListCell<Line> call(ListView<Line> newLine)
{
return new ListCell<Line>()
{
#Override
protected void updateItem(Line item, boolean y)
{
super.updateItem(item, false);
setGraphic(item);
} // end updateItem
} ; // end new ListCell
} // end call()
}); // end setCellFactory
primaryStage.setScene(new Scene(root, 150, 300));
primaryStage.show();
}
Line line1 = new Line(15, 0, 50, 0), line2 = new Line(15, 0, 50, 0), line3 = new Line(15, 0, 50, 0);
final ObservableList<Line> DASHUNIT = FXCollections.observableArrayList(line1, line2, line3);
}
Finally, I got it working with the code below. Following kleopatra's comments, the insipartion came from the official Oracle page https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ComboBox.html
ComboBox<Line> dashCombo = new ComboBox<>();
dashCombo.getStylesheets().add("style.css");
dashCombo.setStyle("-fx-min-width: 100; -fx-max-width: 100; -fx-pref-width: 100; "
+ "-fx-min-height: 15; -fx-pref-height: 15; -fx-max-height: 15;");
initDashLineCombo(dashCombo, common.General.DASHUNIT, (numberCoils - i), colCombo);
public static void initDashLineCombo(ComboBox<Line> cb, ObservableList<int[]> list, int i, ColorPicker cp)
{
for(int j = 0; j < list.size(); j++) // cycle over the list and generate a line with dashing defined by list
{
int[] dash = list.get(j);
String dashString = " ";
for(int k : dash)
dashString += k + " ";
Line line = new Line(25, 0, 95, 0);
line.setStrokeWidth(2); line.strokeProperty().bind(cp.valueProperty());
line.setStyle("-fx-stroke-dash-array:" + dashString + ";");
cb.getItems().add(line);
}
cb.getSelectionModel().clearAndSelect(i-1);
cb.setCellFactory(new Callback<ListView<Line>, ListCell<Line>>()
{
#Override public ListCell<Line> call(ListView<Line> p)
{
return new ListCell<Line>() {
private final Line line;
{
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
line = new Line(0, 0, 70, 0);
line.setStrokeWidth(2);
line.strokeProperty().bind(cp.valueProperty());
} // end Line
#Override protected void updateItem(Line item, boolean empty)
{
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
}
else {
line.setStyle(item.getStyle());
setGraphic(line);
setItem(line);
}
} // end updateItem()
}; // end ListCell
} // end call()
}); // end setCellFactory
}
enter image description here
enter image description here
in qml, i create a ChartView:
ChartView {
id : mChart
title: "Bash History Graph"
anchors.fill: parent
legend.alignment: Qt.AlignBottom
antialiasing: true
animationOptions: ChartView.SeriesAnimations
}
and in Component.onCompleted signal:
Component.onCompleted: {
var mHorSeries = Qt.createQmlObject('import QtCharts 2.2; HorizontalBarSeries {}', mChart);
var categoryAxisY=Qt.createQmlObject('import QtCharts 2.2;BarCategoryAxis {}',mChart);
var mBarSet = Qt.createQmlObject('import QtCharts 2.2; BarSet{}',mHorSeries);
categoryAxisY.categories = ["2007", "2008", "2009", "2010"];
mHorSeries.append("commands", [2, 2, 3, 4, 5, 6]);
mChart.setAxisY(categoryAxisY, mHorSeries);
}
this is the output.
any idea to fix the issue?
thanks.
A simple option to create series is to use createSeries(), then we add the axisY and the other properties:
ChartView {
id : mChart
title: "Bash History Graph"
anchors.fill: parent
antialiasing: true
animationOptions: ChartView.SeriesAnimations
legend.alignment: Qt.AlignBottom
Component.onCompleted: {
var mHorSeries = mChart.createSeries(ChartView.SeriesTypeHorizontalBar)
var categoryAxisY=Qt.createQmlObject('import QtCharts 2.2;BarCategoryAxis {}',mChart);
mHorSeries.axisY = categoryAxisY
categoryAxisY.categories = ["2007", "2008", "2009", "2010", "2011", "2012" ]
var mBarSet = mHorSeries.append("commands", [2, 2, 3, 4, 5, 6])
mChart.axisX(mHorSeries).min= 0 //Math.min.apply(null, mBarSet.values)
mChart.axisX(mHorSeries).max= 10 //Math.max.apply(null, mBarSet.values)
}
}
I am currently working for my school project and using Meteor with AngularJs 1 and ES6. In one of my views I try to update some live data (with AngularCharts) every second which are currently randomly generated. I am new to the way of how Meteor and ES6 works, so I think a have a pretty easy error.
This is my code of the class from the view:
class View2 {
constructor($interval, $scope) {
'ngInject';
this.cardRow = [
{name: 'Drilling Heat', color: 'white', value: 0},
{name: 'Drilling Speed', color: 'white', value: 0},
{name: 'Milling Heat', color: 'white', value: 0},
{name: 'Milling Speed', color: 'white', value: 0}
];
this.type = ['bar', 'line', 'pie', 'doughnut', 'radar'];
this.chartRow = [
{
name: 'Chart1',
type: 'bar',
labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
series: ['Series A'],
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
datasetOverride: [{yAxisID: 'y-axis-1'}],
options: {
animation: false,
scales: {
yAxes: [
{
id: 'y-axis-1',
type: 'linear',
display: true,
position: 'left'
}]
}
}
},
{
name: 'Chart2',
type: 'line',
labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
series: ['Series A'],
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
datasetOverride: [{yAxisID: 'y-axis-1'}],
options: {
animation: false,
scales: {
yAxes: [
{
id: 'y-axis-1',
type: 'linear',
display: true,
position: 'left'
}]
}
}
}
];
$interval(this.update, 1000);
}
update() {
for (var i = 0; i < this.cardRow.length; i++) {
this.cardRow[i].value = Math.round((Math.random() * 10) * 10);
var value = this.cardRow[i].value;
switch (true) {
case (value > 80):
this.cardRow[i].color = 'red';
break;
case (value > 60):
this.cardRow[i].color = 'orange';
break;
case (value > 40):
this.cardRow[i].color = 'yellow';
break;
default:
this.cardRow[i].color = 'green';
break;
}
}
for (var y = 0; y < this.chartRow.length; y++) {
for (var z = 0; z < this.chartRow[y].data.length; z++) {
this.chartRow[y].data[z] = this.chartRow[y].data[z + 1];
}
this.chartRow[y].data[z - 1] = Math.round((Math.random() * 10) * 10);
}
}
}
The $interval should call the function "update" every second but then the variable is unknown. it throws an error like this:
TypeError: Cannot read property 'length' of undefined
at update (view2.js:74)
at callback (modules.js?hash=7db65c4…:46346)
at Scope.$eval (modules.js?hash=7db65c4…:51381)
at Scope.$digest (modules.js?hash=7db65c4…:51194)
at Scope.$apply (modules.js?hash=7db65c4…:51489)
at tick (modules.js?hash=7db65c4…:46336)
What can I do to solve this problem? And is there a way to use Meteor with the old Javascript Version?
I'm using iron-router and chartist, and I have a template chart that shows the chart when I refresh the page, but not when I navigate away and come back. The div still exists, and the space is taken up, but the chart isn't showing.
Template.chart.onRendered(function () {
new Chartist.Line('.ct-chart', {
labels: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
series: [
[12, 9, 7, 8, 5],
[2, 1, 3.5, 7, 3],
[1, 3, 4, 5, 6]
]
}, {
fullWidth: true,
chartPadding: {
right: 40
}
});
});
https://atmospherejs.com/mfpierre/chartist-js
Below does work, but it's clearly not the correct way to do this:
Template.chart.onRendered(function () {
setTimeout(function() {
new Chartist.Line('.ct-chart', {
labels: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
series: [
[12, 9, 7, 8, 5],
[2, 1, 3.5, 7, 3],
[1, 3, 4, 5, 6]
]
}, {
fullWidth: true,
chartPadding: {
right: 40
}
});
},2000);
});
In my router.js:
Router.map(function() {
this.route('join');
this.route('chart');
//etc.
})
I cannot reproduce your error. The following works just fine and I can navigate between the two routes without problems (the chart reappears when navigating back from other).
HTML:
<head>
<title>chartist</title>
</head>
<template name="chart">
<div class="ct-chart">
Chart
</div>
{{#linkTo route="other"}}other{{/linkTo}}
</template>
<template name="other">
Other
{{#linkTo route="chart"}}chart{{/linkTo}}
</template>
JS:
if (Meteor.isClient) {
Template.chart.onRendered(function () {
new Chartist.Line('.ct-chart', {
labels: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
series: [
[12, 9, 7, 8, 5],
[2, 1, 3.5, 7, 3],
[1, 3, 4, 5, 6]
]
}, {
fullWidth: true,
chartPadding: {
right: 40
}
});
});
}
Router.route('/chart', function () {
this.render('chart');
});
Router.route('/other', function () {
this.render('other');
});
I am trying to draw a chart as below but the x axis stops at end of year 11 which it does not at the moment. I set max to 19 but it did not work.How can I get rid of those grey lines after end of Year 11?
Also for x-axis labels I want to decrease the font size of second category ([1,2,3,4]) and Year with bigger font but in label styles the font size property applies to all labels.
var l=19;
var m=-0.6;
new Highcharts.Chart({
chart: {
renderTo: elementId,
spacingLeft: 10,
spacingRight: 10
},
title: {
text: subject
},
xAxis: {
categories: [{
name: "Year 7",
categories: [1, 2, 3, 4]
}, {
name: "Year 8",
categories: [1, 2, 3, 4]
}, {
name: "Year 9",
categories: [1, 2, 3, 4]
}, {
name: "Year 10",
categories: [1, 2, 3, 4]
}, {
name: "Year 11",
categories: [1, 2, 3, 4]
}],
labels: {
style: {
fontSize: '7.5px'
}
},
plotLines: [{
color: '#5DA06E',
width: 2,
value: l
}, {
color: '#5DA06E',
width: 2,
value: -1
}],
//max: l
},
yAxis: [{
labels: {
enabled: false
},
title: {
text: null
},
min: 0,
max: 1000
},
{
title: {
text: null
},
labels: {
style: {
fontSize: '7.5 px'
},
align: 'left',
x: 3,
formatter: function () {
var value = change[this.value];
return value !== 'undefined' ? value : this.value;
}
},
tickPositions: [0, 280, 360, 440, 520, 600, 680, 760, 840, 920, 1000],
gridLineColor: 'white',
opposite: true,
min: 0,
max: 1000
}],
series: [{
type: 'line',
data: [[m, 0], [l, 280]],
color: '#A5DEC1',
}, {
type: 'line',
data: [[m, 80], [l, 360]],
color: '#94D0A3',
},
...
strong text
Are m and l params constant? Or can you change them? If yes, then see: http://jsfiddle.net/TFhd7/373/
In short: Categories reserves a place from -0.5 to 0.5 with category index. For example Year7 -> 4 means x-values from 3.5 to 4.5. So according to this information let's change that values:
var l = 19.5;
var m = -0.5;
Now modify extremes and plotLines:
plotLines: [{
color: '#5DA06E',
width: 2,
value: l
}, {
color: '#5DA06E',
width: 2,
value: m
}],
max: l - 0.5,
min: m + 0.5,