how do i update a Google Charts pie chart to reflect filtered data but retain filtered out segments as a diff colour - dynamic

I'm new to Google Charts and I'm struggling to solve this.
I have a datatable (called "result" in the code)
Name Liquidity percent
a 1.3 20%
b 2.0 20%
c 3.4 20%
d 4 20%
e 5 20%
My pie chart is set to show 5 segments of equal size - 20% - and each segment is blue
I have set a 'Number Range Filter' control wrapper to filter the liquidity - when i set the control to the range 1 to 4 the pie moves to 4 equal sized segments.
BUT... I don't want it to do this. Instead of 1 segment disappearing I want the 5 segments to remain visible and the colour of the filtered segment to change to be a different colour.
The aim being that I can see visually a total percentage that falls within the number filter.
EDIT:
So I've had a mess about and this is as far as I've got incorporating dlaliberte's comment below.
function drawChart3(chartData3) {
var result = google.visualization.arrayToDataTable(chartData3,false); // 'false' means that the first row contains labels, not data.
var chart3 = new google.visualization.ChartWrapper({
'chartType': 'PieChart',
'containerId': 'chart3_div',
'dataTable': result,
'options': {
'width': 500,
'height': 500,
'legend': {position: 'none'},
'pieSliceText': 'none',
'colors': ['blue']
},
'view': {'columns': [0 , 1]}
});
var liquidityDT = new google.visualization.DataTable();
// Declare columns
liquidityDT.addColumn('number', 'Liquidity');
// Add data.
liquidityDT.addRows([
[1],
[2],
[3],
[4],
[5],
]);
// Create a range slider, passing some options
var liquidityRangeSlider = new google.visualization.ControlWrapper({
'controlType': 'NumberRangeFilter',
'containerId': 'filter3_div',
'dataTable': liquidityDT,
'options': {
'filterColumnLabel': 'Liquidity',
'minValue': 0,
'maxValue': 5
}
});
liquidityRangeSlider.draw();
chart3.draw();
google.visualization.events.addListener(liquidityRangeSlider, 'statechange', setChartColor);
function setChartColor(){
var state = liquidityRangeSlider.getState();
var stateLowValue = state.lowValue;
var stateHighValue = state.highValue;
for (var i = 0; i < result.getNumberOfRows(); i++) {
var testValue = result.getValue(i,2);
if (testValue < stateLowValue || testValue > stateHighValue){
alert("attempting to set colors")
//this bit I have no clue how to change the color of the table row currently being iterated on
chart3.setOption({'colors' : ['red']});
}
}
chart3.draw();
}
}
so it produces the pie chart with 5 blue segments. I can move the number filter and it fires the listener event but I can't get it to affect anything on the piechart (Chart3) - The code currently attempts just to change the whole chart to RED but that isn't even working never mind the just colouring the filtered segments
So how do I effect the changes into Chart3 and how do I only effect the filtered segments?.
any clues welcome?
thanks

You can (and must, for your application) use a NumberRangeFilter outside of a Dashboard because it will otherwise always do the data filtering. Instead, just listen for the change events on the NumberRangeFilter, get its current state, and then iterate through the colors array for your chart to set the appropriate colors. You'll have to draw the chart again with the updated colors. Here is the loop to set the colors and redraw.
var colors = [];
for (var i = 0; i < result.getNumberOfRows(); i++) {
var color = (testValue < stateLowValue || testValue > stateHighValue) ? 'red' : 'blue';
colors.push(color);
}
chart3.setOption('colors', colors);
chart3.draw();

Related

set the position of data label according to vertical bar chart column height

I am using vue chat js with chart js 2.9.4 package. And chart js plugin datalabels also plugged in to show data labels. I created a vertical bar chart and when some columns height is lower, data label is overlapped with x-axis. In order to avoid that I did as follows.
plugins: {
datalabels: {
align(context) {
const index = context.dataIndex
const value = context.dataset.data[index]
return value < 10 ? 'top' : 'center'
},
}
}
There as you can see I had to add a condition manually to set the position of the data label. If the value is less than 10, then data label should be on top and if it's more than or equal to 10 it should be on center. But you know that value 10 in the condition should be dynamic. So is there any way to identify this overlap behavior in the used plugin or any good equation to fix this issue?
Take the max value of the dataset you are showing
const data = context.dataset.data
const max = Math.max( ...data )
Then, if for the given bar it is lower than the 10% (or some other percentil) of the max bar value then just do what you where doing.
All together:
const index = context.dataIndex
const data = context.dataset.data
const max = Math.max( ...data )
const percentil = 0.10
const value = data[index]
return value < max * percentil ? 'top' : 'center'

nvd3 date comparison on x axis

As you can see on the photo, I pushed 2 different series to the nvd3 with datum function. Current line shows today and previous line shows previous range of date of data. What If I want to merge it on single range with first array of series, which means current. What sould I do on nvd3?
nv.addGraph(() => {
var height = 400;
var chart = nv.models.lineChart().useInteractiveGuideline(true).showLegend(true).showYAxis(true).showXAxis(true);
//chart.xAxis.axisLabel('Date').tickValues((d) => d3.time.format("%Y-%m-%d")(new Date(d)));
chart.yAxis.axisLabel('Voltage (v)').tickFormat(d3.format('.02f'));
this.redraw(chart)
this.chartRef = chart
});
d3.select(this.$refs.chart).datum(this.metrics.series).call(chart);
You can find the example of series below:
{"series":[{"key":"Current","values":[{"x":1609680551000,"y":"0.00"},{"x":1609766951000,"y":"0.00"},{"x":1609853351000,"y":"0.00"},{"x":1609939751000,"y":"0.00"},{"x":1610026151000,"y":"0.00"},{"x":1610112551000,"y":"0.00"},{"x":1610198951000,"y":"0.00"},{"x":1610285351000,"y":"0.00"}]},{"key":"Previous","values":[{"x":1609075751000,"y":"0.00"},{"x":1609162151000,"y":"0.00"},{"x":1609248551000,"y":"0.00"},{"x":1609334951000,"y":"0.00"},{"x":1609421351000,"y":"0.00"},{"x":1609507751000,"y":"0.00"},{"x":1609594151000,"y":"0.00"},{"x":1609680551000,"y":"0.00"}]}]}

Make line chart with values and dates

In my app i use ios-charts library (swift alternative of MPAndroidChart).
All i need is to display line chart with dates and values.
Right now i use this function to display chart
func setChart(dataPoints: [String], values: [Double]) {
var dataEntries: [ChartDataEntry] = []
for i in 0..<dataPoints.count {
let dataEntry = ChartDataEntry(value: values[i], xIndex: i)
dataEntries.append(dataEntry)
}
let lineChartDataSet = LineChartDataSet(yVals: dataEntries, label: "Items count")
let lineChartData = LineChartData(xVals: dataPoints, dataSet: lineChartDataSet)
dateChartView.data = lineChartData
}
And this is my data:
xItems = ["27.05", "03.06", "17.07", "19.09", "20.09"] //String
let unitsSold = [25.0, 30.0, 45.0, 60.0, 20.0] //Double
But as you can see - xItems are dates in "dd.mm" format. As they are strings they have same paddings between each other. I want them to be more accurate with real dates. For example 19.09 and 20.09 should be very close. I know that i should match each day with some number in order to accomplish it. But i don't know what to do next - how i can adjust x labels margins?
UPDATE
After small research where i found out that many developers had asked about this feature but nothing happened - for my case i found very interesting alternative to this library in Swift - PNChart. It is easy to use, it solves my problem.
The easiest solution will be to loop through your data and add a ChartDataEntry with a value of 0 and a corresponding label for each missing date.
In response to the question in the comments here is a screenshot from one of my applications where I am filling in date gaps with 0 values:
In my case I wanted the 0 values rather than an averaged line from data point to data point as it clearly indicates there is no data on the days skipped (8/11 for instance).
From #Philipp Jahoda's comments it sounds like you could skip the 0 value entries and just index the data you have to the correct labels.
I modified the MPAndroidChart example program to skip a few data points and this is the result:
As #Philipp Jahoda mentioned in the comments the chart handles missing Entry by just connecting to the next data point. From the code below you can see that I am generating x values (labels) for the entire data set but skipping y values (data points) for index 11 - 29 which is what you want. The only thing remaining would be to handle the x labels as it sounds like you don't want 15, 20, and 25 in my example to show up.
ArrayList<String> xVals = new ArrayList<String>();
for (int i = 0; i < count; i++) {
xVals.add((i) + "");
}
ArrayList<Entry> yVals = new ArrayList<Entry>();
for (int i = 0; i < count; i++) {
if (i > 10 && i < 30) {
continue;
}
float mult = (range + 1);
float val = (float) (Math.random() * mult) + 3;// + (float)
// ((mult *
// 0.1) / 10);
yVals.add(new Entry(val, i));
}
What I did is fully feed the dates for x data even no y data for it, and just not add the data entry for the specific xIndex, then it will not draw the y value for the xIndex to achieve what you want, this is the easiest way since you just write a for loop and continue if you detect no y value there.
I don't suggest use 0 or nan, since if it is a line chart, it will connect the 0 data or bad things will happen for nan. You might want to break the lines, but again ios-charts does not support it yet (I also asked a feature for this), you need to write your own code to break the line, or you can live with connecting the 0 data or just connect to the next valid data.
The down side is it may has performance drop since many xIndex there, but I tried ~1000 and it is acceptable. I already asked for such feature a long time ago, but it took lot of time to think about it.
Here's a function I wrote based on Wingzero's answer (I pass NaNs for the entries in the values array that are empty) :
func populateLineChartView(lineChartView: LineChartView, labels: [String], values: [Float]) {
var dataEntries: [ChartDataEntry] = []
for i in 0..<labels.count {
if !values[i].isNaN {
let dataEntry = ChartDataEntry(value: Double(values[i]), xIndex: i)
dataEntries.append(dataEntry)
}
}
let lineChartDataSet = LineChartDataSet(yVals: dataEntries, label: "Label")
let lineChartData = LineChartData(xVals: labels, dataSet: lineChartDataSet)
lineChartView.data = lineChartData
}
The solution which worked for me is splitting Linedataset into 2 Linedatasets. First would hold yvals till empty space and second after emptyspace.
//create 2 LineDataSets. set1- till empty space set2 after empty space
set1 = new LineDataSet(yVals1, "DataSet 1");
set2= new LineDataSet(yVals2,"DataSet 1");
//load datasets into datasets array
ArrayList<ILineDataSet> dataSets = new ArrayList<ILineDataSet>();
dataSets.add(set1);
dataSets.add(set2);
//create a data object with the datasets
LineData data = new LineData(xVals, dataSets);
// set data
mChart.setData(data);

issue with different type of axes for storyboard example of dimplejs

I'm trying to create a storyboard very similar to the one in dimpleJS examples. My data looks like below.
[{"Date":"2014-05-05T19:03:23Z","Liters":0.23,"Soda":"Coke","Day":"2014-05-05","Time of Day":"15:03","Day-Time":"2014-05-05 15:03"},{"Date":"2014-05-05T19:37:27Z","Liters":0.35,"Soda":"Coke","Day":"2014-05-05","Time of Day":"15:37","Day-Time":"2014-05-05 15:37"},{"Date":"2014-05-05T20:33:33Z","Liters":0.21,"Soda":"Coke","Day":"2014-05-05","Time of Day":"16:33","Day-Time":"2014-05-05 16:33"},{"Date":"2014-05-05T21:11:37Z","Liters":0.21,"Soda":"Coke","Day":"2014-05-05","Time of Day":"17:11","Day-Time":"2014-05-05 17:11"},{"Date":"2014-05-06T13:55:09Z","Liters":0.22,"Soda":"Coke","Day":"2014-05-06","Time of Day":"9:55","Day-Time":"2014-05-06 9:55"},{"Date":"2014-05-06T14:27:13Z","Liters":0.27,"Soda":"Coke","Day":"2014-05-06","Time of Day":"10:27","Day-Time":"2014-05-06 10:27"},{"Date":"2014-05-06T15:42:21Z","Liters":0.14,"Soda":"Coke","Day":"2014-05-06","Time of Day":"11:42","Day-Time":"2014-05-06 11:42"},{"Date":"2014-05-06T16:16:24Z","Liters":0.24,"Soda":"Coke","Day":"2014-05-06","Time of Day":"12:16","Day-Time":"2014-05-06 12:16"},{"Date":"2014-05-05T19:13:24Z","Liters":0.59,"Soda":"Diet Coke","Day":"2014-05-05","Time of Day":"15:13","Day-Time":"2014-05-05 15:13"},{"Date":"2014-05-05T20:33:33Z","Liters":0.01,"Soda":"Diet Coke","Day":"2014-05-05","Time of Day":"16:33","Day-Time":"2014-05-05 16:33"},{"Date":"2014-05-05T21:04:36Z","Liters":0.39,"Soda":"Diet Coke","Day":"2014-05-05","Time of Day":"17:04","Day-Time":"2014-05-05 17:04"},{"Date":"2014-05-05T21:11:37Z","Liters":0.21,"Soda":"Diet Coke","Day":"2014-05-05","Time of Day":"17:11","Day-Time":"2014-05-05 17:11"},{"Date":"2014-05-05T21:57:42Z","Liters":0.17,"Soda":"Diet Coke","Day":"2014-05-05","Time of Day":"17:57"},{"Date":"2014-05-06T11:11:53Z","Liters":0.42,"Soda":"Diet Coke"},{"Date":"2014-05-06T12:54:03Z","Liters":0.49,"Soda":"Diet Coke"},{"Date":"2014-05-06T12:55:03Z","Liters":0.48,"Soda":"Diet Coke"},{"Date":"2014-05-06T13:07:04Z","Liters":0.27,"Soda":"Diet Coke"},{"Date":"2014-05-06T13:34:07Z","Liters":0.41,"Soda":"Diet Coke"},{"Date":"2014-05-06T13:55:09Z","Liters":0.19,"Soda":"Diet Coke"},{"Date":"2014-05-06T14:27:13Z","Liters":0.01,"Soda":"Diet Coke"},{"Date":"2014-05-06T15:42:21Z","Liters":0.02,"Soda":"Diet Coke"},{"Date":"2014-05-06T16:01:23Z","Liters":0.45,"Soda":"Diet Coke"},{"Date":"2014-05-06T16:05:23Z","Liters":0.52,"Soda":"Diet Coke"},{"Date":"2014-05-06T16:35:27Z","Liters":0.65,"Soda":"Diet Coke"},{"Date":"2014-05-06T16:49:28Z","Liters":0.4,"Soda":"Diet Coke"},{"Date":"2014-05-06T16:50:29Z","Liters":0.14,"Soda":"Diet Coke"},{"Date":"2014-05-05T18:10:18Z","Liters":0.24,"Soda":"Powerade"},{"Date":"2014-05-05T19:03:23Z","Liters":0.01,"Soda":"Powerade"},{"Date":"2014-05-05T19:37:27Z","Liters":0.01,"Soda":"Powerade"},{"Date":"2014-05-05T20:39:34Z","Liters":0.39,"Soda":"Powerade"},{"Date":"2014-05-05T21:04:36Z","Liters":0.01,"Soda":"Powerade"},{"Date":"2014-05-06T10:32:49Z","Liters":0.18,"Soda":"Powerade"},{"Date":"2014-05-06T11:11:53Z","Liters":0.01,"Soda":"Powerade"},{"Date":"2014-05-06T12:54:03Z","Liters":0.01,"Soda":"Powerade"},{"Date":"2014-05-06T14:27:13Z","Liters":0.02,"Soda":"Powerade"},{"Date":"2014-05-06T15:42:21Z","Liters":0.02,"Soda":"Powerade"},{"Date":"2014-05-06T16:01:23Z","Liters":0.03,"Soda":"Powerade"},{"Date":"2014-05-06T16:05:23Z","Liters":0.03,"Soda":"Powerade"},{"Date":"2014-05-06T16:23:25Z","Liters":0.12,"Soda":"Powerade"},{"Date":"2014-05-06T16:50:29Z","Liters":0.01,"Soda":"Powerade"}]
I've category and time axes. The problem is that the bubbles do not show up at the correct y(time) axis. My code looks like below and it also shows too many 0 values although there is no 0 value in dataset.
var series, charts, lastDate = null, sodas = dimple.getUniqueValues(data, "Soda");
charts = [new dimple.chart(svg, null)],
charts.push(myChart);
charts.forEach(function (chart, i) {
var x, y, z;
chart.setBounds(this.attributes.left, this.attributes.top, this.attributes.width - 225, this.attributes.height - 225);
x = chart.addCategoryAxis("x", "Soda");
x.overrideMax = 3;
x.hidden = (i === 0);
y = chart.addTimeAxis("y", "Day-Time", "%Y-%m-%d %H:%M", "%d-%m %H:%M");
// y.overrideMax = "17:00";
y.hidden = (i === 0);
z = chart.addMeasureAxis("z", "Liters");
z.overrideMax = 1;
// Ensure the same colors for every owner in both charts
// differing by opacity
sodas.forEach(function (soda, k) {
chart.assignColor(
soda,
charts[0].defaultColors[k].fill,
"white",
(i === 0 ? 0.3 : 1));
}, this);
}, this);
charts[1].addLegend(850, 100, 60, 300, "Right");
charts[1].setStoryboard("Time of Day", function (d) {
// Use the last date variable to manage the previous tick's data
if (lastDate !== null) {
// Pull the previous data
var lastData = dimple.filterData(data, "Time of Day", lastDate);
// Add a series to the background chart to display old position
var lastSeries = charts[0].addSeries("Soda", dimple.plot.bubble);
// Average suits these measures better
lastSeries.aggregate = dimple.aggregateMethod.avg;
// Give each series its own data at different periods
lastSeries.data = lastData;
// Draw the background chart
charts[0].draw();
// Class all shapes as .historic
lastSeries.shapes.attr("class", "historic");
// Reduce all opacity and remove once opacity drops below 5%
d3.selectAll(".historic").each(function () {
var shape = d3.select(this),
opacity = shape.style("opacity") - 0.02;
// shape.style("opacity", opacity);
if (opacity < 0.1) {
shape.remove();
} else {
shape.style("opacity", opacity);
}
});
}
lastDate = d;
});
series = charts[1].addSeries("Soda", dimple.plot.bubble)
series.aggregate = dimple.aggregateMethod.avg;
// Draw the main chart
charts[1].draw();
Here is the screenshot.
Thanks for adding the fiddles. If you remove the x.hidden = (i === 0); and y.hidden = (i === 0); lines (so that the back chart's axes are displayed) you can see the issue. Unlike measure axes where you can fix the max and min to make sure that the charts are identically proportioned, category axes will only draw for the elements in the data, so a frame with a single category will only draw a single point in the centre of the axis. Also the ordering will change by default.
To get your case working you need to fix ordering for the x axis.
x.addOrderRule(["Coke", "Diet Coke", "Powerade"]);
Define a max and min value for the y axis:
y.overrideMin = d3.time.format("%Y-%m-%d").parse("2014-05-05");
y.overrideMax = d3.time.format("%Y-%m-%d").parse("2014-05-07");
And stick some dummy rows in for any missing categories.
lastData = lastData.concat([
{
"Date": "2014-05-06T00:00:00Z",
"Liters": 0,
"Soda": "Coke"
},
{
"Date": "2014-05-06T00:00:00Z",
"Liters": 0,
"Soda": "Diet Coke"
},
{
"Date": "2014-05-06T00:00:00Z",
"Liters": 0,
"Soda": "Powerade"
}
]);
This results in this output: http://jsfiddle.net/4qBqJ/9/

How to add a text on top of a column with ZedGraph?

How can I add some text on top of a bar in a chart.
This is the code I have to add the bar:
var color = ColorTranslator.FromHtml(row.Colour);
var barItem = graphPane.AddBar(row.Propensity.ToString(), null, Ys.ToArray(), color);
Thank you
Here is a quick example using TextObj to simply add labels to each bar.
GraphPane myPane = zedGraphControl1.GraphPane;
double[] y = { 100, 50, 75, 125 };
BarItem myBar = myPane.AddBar("Data", null, y, Color.Red);
for (int i = 0; i < myBar.Points.Count; i++)
{
TextObj barLabel = new TextObj(myBar.Points[i].Y.ToString(), myBar.Points[i].X, myBar.Points[i].Y + 5);
barLabel.FontSpec.Border.IsVisible = false;
myPane.GraphObjList.Add(barLabel);
}
myBar.Label.IsVisible = true;
zedGraphControl1.AxisChange();
zedGraphControl1.Invalidate();
Of course this just uses the value of the data as the label. If you wanted to use custom labels, you could create a string array or list and use that inside the loop.
Here are some ZedGraph references:
Introduction and examples: http://www.codeproject.com/KB/graphics/zedgraph.aspx
Source code documentation: http://zedgraph.sourceforge.net/documentation/default.html
You need to define AlignH and AlignV in your text object:
TextObj textLabel = new TextObj(value.ToString(), positionX, value, CoordType.AxisXYScale, AlignH.Center, AlignV.Top);
AlignV define the position of your value on the bar.
Providing you want the label text to match the value of each bar, then you can do it with a single line of code:
BarItem.CreateBarLabels(graphPane, false, "F0");
Note that the 3rd parameter is a double.ToString format. e.g. "F0" = 12, "F2" = 12.34
However, if you want any flexibity with it then this solution is not ideal. It also doesn't work very well if you have a stacked bar chart, because having any 0 value data will cause labels to overlap and look horrific