Assume you have the following elements added to your JSXGraph board:
var checkbox1 = board.create('checkbox', [1, 0.5, 'Click me']);
var p = board.create('point', [1, 0], {
visible: function() {return checkbox1.Value();},
label:{offset:[15,15]}
});
Why is it that the presence of the function inside visible alters the values given inside label? (By commenting the line visible..., you notice a difference.)
Yes, indeed using a function as value for the attribute visible seems to have an influence on the position of the label. But the problem is even more involved:
The default value of the attribute anchorY of the label is middle, i.e. with the offset attribute set to [0,0], the label is centered vertically beside the point p.
However, in order to be able to position a label vertically centered, JSXGraph has to know the size of the label's text box. Unfortunately, if the label is created with visible:false, the text size can not be determined (for the moment).
In short, the problem is that the point is created with visible:false.
If your code would be
var checkbox1 = board.create('checkbox', [1, 0.5, 'Click me']);
var p = board.create('point', [1, 0], {
visible: function() {return !checkbox1.Value();},
label:{offset:[15,15]}
});
the label position is as expected.
A possible workaround would be to user a different value for anchorY like:
var checkbox1 = board.create('checkbox', [1, 1.5, 'Click me']);
var p = board.create('point', [1, 0], {
visible: function() {return checkbox1.Value();},
label:{offset:[15,0], anchorY: 'bottom'}
});
See it live at https://jsfiddle.net/1u7geLar/
Related
I used angular-chart.js in my website to create a Pie chart, and I would like to customize the legend. By default, the legend shows the color and the label. (As shown in the picture below) I would like to add the value/data of that label, like what it shown in the tooltip of the chart.
This is my HTML code:
<canvas id="pie" class="chart chart-pie"
chart-data="chartData" chart-labels="chartLabels" chart-options="chartOptions">
</canvas>
Based on the angular-chart.js documentation, legend is now a Chart.js option so the chart-legend attribute has been removed.
That is why, in my JS code I've tried to add generateLabels, just in case this is what I need to customize the legend:
$scope.chartOptions = {
legend: {
display: true,
labels: {
generateLabels: function(chart){
console.log(chart.config);
}
}
}
};
But whenever I add this lines, it will not show the chart. I think it is an error or something. And I'm not sure, if generateLabels is the right option that I needed.
Can somebody teach me the right way to customize the legend to achieve what I wanted?
Thanks in advance!
Let me try shedding some light/answering your question:
generateLabels: does make custom labels,and replaces templates from v1 but in order to use it you have to get your chart information and reimplement legend labels adhering to the Legend Item Interface found in the docs and code. Sounds a bit cryptic, but in practice is somehow simple and goes like this:
var theHelp = Chart.helpers;
// You need this for later
// Inside Options:
legend: {
display: true,
// generateLabels changes from chart to chart, check the source,
// this one is from the doughnut :
// https://github.com/chartjs/Chart.js/blob/master/src/controllers/controller.doughnut.js#L42
labels: {
generateLabels: function(chart) {
var data = chart.data;
if (data.labels.length && data.datasets.length) {
return data.labels.map(function(label, i) {
var meta = chart.getDatasetMeta(0);
var ds = data.datasets[0];
var arc = meta.data[i];
var custom = arc && arc.custom || {};
var getValueAtIndexOrDefault = theHelp.getValueAtIndexOrDefault;
var arcOpts = chart.options.elements.arc;
var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
return {
// And finally :
text: ds.data[i] + "% of the time " + label,
fillStyle: fill,
strokeStyle: stroke,
lineWidth: bw,
hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
index: i
};
});
}
return [];
}
}
}
Result:
Codepen: Chart.js Pie Chart Custom Legend Labels
There are other alternatives, if you notice on the pen/pie, the slices also have data information, that is from a plugin (check the pen)
Still another option, is to render the legend labels off canvas,for instance:
myPieChart.generateLegend();
Which gives you this Html:
"<ul class='0-legend'><li><span style='background-color:black'> </span>she returns it </li><li><span style='background-color:white'></span>she keeps it</li></ul>"
I haven't tried it, but I think you can modify it with the global method for your data Legend on the callback an it will give you a block of Html you can insert off canvas.
I have a requirement where I want my pie chart to display only top 3 data label instead of displaying all and filling the space. Is there any inbuilt highchart api available or a best solution to achieve this?
Here is my jsfiddle:
http://jsfiddle.net/k80ayx1t/
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer',
dataLabels: {
enabled: true
},
showInLegend: true
}
}
I have searched enough and tried to find the solution before posting this question but didn't find any help.
You can use dataLabels.formatter function for returning only specific dataLabels (for points thats value is big enough).
formatter: function() {
var val = this.y,
allData = $.extend(true, [], this.series.processedYData).sort(),
length = allData.length;
if (length - 4 >= 0 && val > allData[length - 4])
return val;
}
I am sorting my yValues and then I am checking if my dataLabels points y value is bigger or equal to the third y value of my series. If it is bigger or equal, I am returning value, else I am not returning anything.
Here you can find an example how it can work: http://jsfiddle.net/k80ayx1t/1/
In my custom directive, I'm adding elements to the DOM based on the number of objects in my datasource array. I need to watch a specific property in each object. As I add these elements to the DOM, I want to set up a $watch on the checked property of each object in the toppings array, but it's not working, and I don't know why. I set up a breakpoint inside the function that should be invoked when the property changes from true to false or false to true, but that function is never invoked. Is the reason obvious? I'm just learning Angular, so I could easily be making a stupid error.
$scope.bits = 66; (i.e. onions and olives)
$scope.toppings = [
{ topping: 1, bits: 2, name: 'onions' },
{ topping: 2, bits: 4, name: 'mushrooms' },
{ topping: 3, bits: 8, name: 'peppers' },
{ topping: 4, bits: 16, name: 'anchovies' },
{ topping: 5, bits: 32, name: 'artichokes' },
{ topping: 6, bits: 64, name: 'olives' },
{ topping: 7, bits: 128, name: 'sausage' },
{ topping: 8, bits: 256, name: 'pepperoni' }
]
Each object in the model gets a new checked property which will be true or false.
NOTE: the object array will at most contain a dozen or so items. Performance is not a concern.
link: function link(scope, iElement, iAttrs, controller, transcludeFn) {
<snip>
// At this point scope.model refers to $scope.toppings. Confirmed.
angular.forEach(scope.model, function (value, key) {
// bitwise: set checked to true|false based on scope.bits and topping.bits
scope.model[key].checked = ((value.bits & scope.bits) > 0);
scope.$watch(scope.model[key].checked, function () {
var totlBits = 0;
for (var i = 0; i < scope.model.length; i++) {
if (scope.model[i].checked) totlBits += scope.model[i].bits;
}
scope.bits = totlBits;
});
});
<snip>
Array of Objects:
$scope.toppings = [
{ topping: 1, bits: 2, name: 'onions' },
{ topping: 2, bits: 4, name: 'mushrooms' },
{ topping: 3, bits: 8, name: 'peppers', checked:undefined /*may be*/ }
];
Watch using AngularJs $WatchCollection:
Instead of monitoring objects array above, that can change for any property in the object, we will create an array of properties of the elements for which we are watching the collection (.checked).
We filter the array's elements to only monitor those elements which have .checked defined and map that to an array for angular watchCollection.
When a change fires, I will compare the old and new arrays of (.checked) to get exact changed element using lodash difference method.
$scope.$watchCollection(
// Watch Function
() => (
$scope
.toppings
.filter(tp => tp.checked !== undefined)
.map(tp => tp.checked)
),
// Listener
(nv, ov) => {
// nothing changed
if(nv == ov || nv == "undefined") return;
// Use lodash library to get the changed obj
let changedTop = _.difference(nv,ov)[0];
// Here you go..
console.log("changed Topping", changedTop);
})
You use MAP to collect all of the property values you need + convert them into a small string representation (in this case 1 and 0) and then join them together into a string that can be observed.
A typescript example:
$scope.$watch(
() => this.someArray.map(x => x.selected ? "1" : "0").join(""),
(newValue, oldValue, scope) => this.onSelectionChanged(this.getSelectedItems()));
The watchExpression parameter to $scope.$watch should either be a string or a function. I've not experimented extensively with this (I try and avoid explicit watches where possible) but I think it does also work when you watch 'simple' scope properties as object references, but not so well with more complex references.
I think if you supply the reference as a string, e.g. 'model[' + key + '].checked' then you may have some success (I only say this because I've done something similar with $watchCollection previously).
Alternatively you should be able to supply a function, e.g.
$scope.$watch(function() { return scope.model[key].checked; }, function() { ... });
Hope this helps!
Use $watchCollection instead.
From docs:
$watchCollection(obj, listener);
Shallow watches the properties of an object and fires whenever any of the properties change (for arrays, this implies watching the array items; for object maps, this implies watching the properties). If a change is detected, the listener callback is fired.
The obj collection is observed via standard $watch operation and is examined on every call to $digest() to see if any items have been added, removed, or moved.
The listener is called whenever anything within the obj has changed. Examples include adding, removing, and moving items belonging to an object or array.
A lot of code examples use Ext.apply when setting properties on a component in the initComponent method.
Example :
initComponent: function(){
Ext.apply(this, {
items: {
xtype: 'button'
}
})},
My question is, what is the difference in doing it that way, compared to doing it this way :
initComponent: function(){
this.items = {
xtype: 'button'
}
}
For me it seems more readable that way. But am I missing something that I get from Ext.apply?
Ext.apply() is used to simplify the copying of many properties from a source to a target object (most of the time the source and target objects have different sets of properties) and it can in addition be used to apply default values (third argument).
Note that it will not make deep clones! Meaning if you have a array or a object as property value it will apply the reference!
There is also a applyIf() which only copies properties that do not already exist in the target object. In some cases both implementations have also the benefit of dropping the reference of the copied object.
Note:
Your second way won't work because you are missing this.
initComponent: function(){
items = {
xtype: 'button'
}
}
wouldn't initialize anything, you mean
initComponent: function(){
this.items = {
xtype: 'button'
}
}
which does the same like your example using Ext.apply. But Ext.apply shows its power in more complex cases, e.g.
var x = {a: 1, c:3, e:5};
Ext.apply(x, {b:2, d:4, f:6});
console.log(x); // Object {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}
This is often used to overwrite default options of components with given init parameters.
I have a GMap2 object with a GOverviewMapControl and a GAdsManager, but they both appear in the lower right hand corner.
How can I get them to appear in different parts of the map?
Here's my Overview:
map.addControl(new GLargeMapControl());
map.addControl(new GMapTypeControl());
map.addControl(new GOverviewMapControl());
map.enableDoubleClickZoom();
here's my Ads
var adsManagerOptions = {
maxAdsOnMap: 2,
style: 'adunit',
channel: 'XXXXXXXXX'
};
adsManager = new GAdsManager(map, publisherID, adsManagerOptions);
adsManager.enable();
The position property of the GAdsManagerOptions class will allow you to set the position of the GAdsManager. The options are passed in to the GAdsManager constructor.
Unfortunately, the GOverviewMapControl is not as flexible. According to the documentation, "Unlike other controls, you can only place this control in the bottom right corner of the map (G_ANCHOR_BOTTOM_RIGHT)."
I have no way to test this at the moment, but your options will probably look something like:
var adsManagerOptions = {
maxAdsOnMap: 2,
style: 'adunit',
channel: 'XXXXXXXXX',
position: new GControlPosition(G_ANCHOR_BOTTOM_LEFT)
};