I'm using D3 v4 and Datamaps in my angular project.
I try to make the zoom working but I have no clue what's the problem.
I have a function that creates the map:
public createChart() {
this.geoChart = new Datamap({
element: this.geolocationChart.nativeElement,
scope: 'world',
width: null,
height: null,
projection: 'mercator',
responsive: true,
geographyConfig: {
hideAntarctica: true,
borderWidth: 1,
borderOpacity: 1,
borderColor: '#ffffff',
popupTemplate: function(geo, data) {
return ['<div class="hoverinfo"><strong>', geo.properties.name, ': ' + data.countryData, '</strong></div>'].join('');
},
popupOnHover: true,
highlightOnHover: true,
highlightFillColor: '#ff4200',
highlightBorderWidth: 2,
highlightBorderOpacity: 1
},
fills: {
defaultFill: '#f5f5f5',
highlight: '#5005a0'
},
data: this.convertData(this.defaultCountryData),
done: function(datamap) {
datamap.svg.call(d3.zoom().on('zoom', redraw));
function redraw() {
datamap.svg.selectAll('g').attr('transform', d3.event.transform);
}
}
});
}
I trigger it in ngAfterContentInit.
When I try to use the mouse wheel to zoom or drag the map, I receive this error message in the console:
"Cannot read property 'button' of null"
I'm importing D3 and Datamaps like this:
import * as d3 from 'd3';
import Datamap from 'datamaps/dist/datamaps.world.min.js';
I'm really clueless what could be the problem. I've seen other posts and everyone mentioned to import D3 just like I did. Any ideas?
So after more research I write this code:
function(datamap) {
d3.select('.datamap').call(
d3
.zoom()
.scaleExtent([0.7, 6])
.on('zoom', function() {
datamap.svg.selectAll('g').attr('transform', d3.event.transform);
})
);
}
It runs after the chart has loaded, and fortunately it works. For some reason when you call the zoom() function on the datamap.svg it cause an error while datamap.svg and d3.select('.datamap') creates a similar array but d3.select create a selection. If anyone know why it doesn't work the other way please explain it.
Related
Using Mapbox GL, I want to be able to create polygons and give them individual colors, but after along search and read through the api doc, just can'tfigure out out this is supposed to be done.
nb: I want to use draw not addlayer as I want the polygon to still be editable afterwards.
nb: this.color is a hex value selected by the user
maybe I'm a newbie n00bing out, maybe the approach isn't quite right.
// when a poly is created add a color property to it and save it into the stagedFeatures array
this.map.on('draw.create', e => {
e.features.forEach(feature => {
feature['properties'] = { color: this.color }
this.stagedFeatures.push(feature)
})
})
// set the color of the next polygon
document.getElementById('colourSelector').addEventListener('change', e => {
if (e.target['value']) {
this.color = e.target['value']
this.setStyle()
}
})
// generate the styles
getStyles() {
return [
{
'id': 'gl-draw-polygon-fill-inactive',
'type': 'fill',
'filter': [
'all',
['==', 'active', 'false'],
['==', '$type', 'Polygon'],
['!=', 'mode', 'static']
],
'paint': {
// my problem is likely here, have tried many expressions etc but mapbox
// expressions aren't super easy to figure out
'fill-color': this.color,
'fill-outline-color': this.color,
'fill-opacity': 0.5
}
},
...
// redraw the map and readd the polygons
setStyle() {
this.map.removeControl(this.draw)
if (typeof this.map.getSource('staged-features') !== 'undefined') {
this.map.removeSource('staged-features')
}
this.map.addSource("staged-features", {
type: "geojson",
data: {
type: "FeatureCollection",
features: this.stagedFeatures
},
})
this.draw = new MapboxDraw({
userProperties: true,
displayControlsDefault: false,
controls: {
polygon: true,
trash: true
},
styles: this.getStyles()
})
this.map.addControl(this.draw)
// loop through any features that the map needs to redraw
this.stagedFeatures.forEach(i => {
console.warn('restore polygons', i)
this.draw.add(i)
})
}
whats happening is the polys redraw but using only the selected color, I'm wanted to get an expression workeing the pulls back the color from the feature.properties.color value
['get', 'color'] with a fallback to this.color doesn't appear to work here.
whats happening is the polys redraw but using only the selected color, I'm wanted to get an expression workeing the pulls back the color from the feature.properties.color value
['get', 'color'] with a fallback to this.color doesn't appear to work here.
https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/
tried the null operators suggested here:
https://github.com/mapbox/mapbox-gl-js/issues/5761
I am using highchart on react-native with this library. I have created simple activity gauge component from high chart official site. Here is the code of component.
import ChartView from 'react-native-highcharts';
import React, { Component } from 'react';
export default class Speedometer extends Component {
render() {
var Highcharts='Highcharts';
var conf={
chart: {
type: 'solidgauge',
height: '110%',
},
....
series: [{
name: 'Move',
data: [{
color: Highcharts.getOptions().colors[0],
radius: '112%',
innerRadius: '88%',
y: 80
}]
}, {
name: 'Exercise',
data: [{
color: Highcharts.getOptions().colors[1],
radius: '87%',
innerRadius: '63%',
y: 65
}]
}, {
name: 'Stand',
data: [{
color: Highcharts.getOptions().colors[2],
radius: '62%',
innerRadius: '38%',
y: 50
}]
}]
};
const options = {
global: {
useUTC: false
},
lang: {
decimalPoint: ',',
thousandsSep: '.'
}
};
return (
<ChartView style={{height:300}} more guage config={conf} options={options}></ChartView>
);
}
}
When i render this component on my screen i get the error
TypeError: TypeError: TypeError: TypeError: undefined is not a function (evaluating 'Highcharts.getOptions()')
How can i use getOptions or colors or theme or other variable with highchart inside react-native component.
You need to understand a few things here:
react-native-highcharts library create a dynamic html content and then inject into webview. Whatever is passed in the config props of ChartView is converted in to string content after flattenObject function inside the library.
If you look at the starting code of html content inside the library you would see that some javascript dependencies has been included and one of them is highcharts. This highcharts library will make the variable Highcharts in the local scope of javascript inside webview.
You are getting this error because React thinks that Highchart must be define somewhere in the component and define Highchart as a string, so if you access string.function it will throw error.
(Solution) you have two option here either to tweak the code inside the library and make it to accept flat string as props and pass this string directly to the ChartView, or you can create dummy Highchart object inside your root component to make the React stop complaining about the Highchart object. Once this object is passed through CharView highchart would be available in javascript scope inside webview and BOOM you Charts are loading.
Now you know the problem you can come up with more solutions. Hope this helps!
Well, here I am again with my Angular and javascript woes feeling dumber for each question I ask.
But let me try to explain my initial steps and how it lead to this problem. So, in my latest project, I wanted to throw some fancy charts to make things cleaner and more approachable for the user. Chart.js came to mind... or I should say, ng2-charts.
Things were peachy, stuff looked nice, but... I had two problems. On a horizontal bar, I wanted to dynamically alter the size of the chart, so the user didn't have to scroll for an obscure amount of time to get down the page. So instead of this unwieldy beast...
I try to apply some Angular magic. Later on I want to calculate the size of it on my backend end. For now, a static value shall do.
#ViewChild('inventoryChart') itemChart : ElementRef;
constructor(public renderer: Renderer2) { }
ngAfterViewInit() {
this.renderer.setStyle(this.itemChart.nativeElement, 'height', '230px')
}
Which leads to...
Nice. But my second problem was a lot harder than I assumed it to be. I wanted the charts to have the respective values on each bar chart. I was somewhat shocked to learn, that it wasn't an innate feature of chart.js but a plugin instead. So I tried to narrow down my problems I had by looking at my chart configuration.
#ViewChild('itemChart') itemChart : ElementRef;
//public context: CanvasRenderingContext2D;
public chartType: string = 'horizontalBar';
chartDatalabel: ChartLabel;
public chartDatasets: Array<any> = [
{ data: [28, 20, 6, 5, 3], label: 'Inventory per country' }
];
public chartLabels: Array<any> = ['DEU', 'SVK', 'FRA', 'GBR', 'AUT'];
public chartColors: Array<any> = [
{
backgroundColor: '#0E599A',
pointBackgroundColor: 'rgba(220,220,220,1)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgba(220,220,220,1)'
},
];
public chartOptions: any = {
responsive: true,
maintainAspectRatio: false,
scales: {
yAxes: [{
barPercentage: .9,
categoryPercentage: .9,
gridLines: {
offsetGridLines: true,
display: true,
},
ticks: {
beginAtZero: true
}
}],
xAxes: [{
ticks: {
beginAtZero: true,
min: 0
},
gridLines: {
offsetGridLines: true,
display: true,
},
}]
},
};
Since I had a similar question and by looking at the documentation... it would require the chart to register the plugin.
So I added
import { Chart } from 'chart.js';
But quickly realized, I didn't know how to actually get this chart instance I was creating in my component. And how could I add this in the option for this specific chart only? I didn't find many sources with a similar problem to mine (which made me feel even more inept...).
Is there any clean 'angular-way' to add this plugin in ng2-charts?
EDIT: Judging from the documentation, I could define what the plugin can do in the following ways...
var plugin = { /* plugin implementation */ };
And then calling this plugin in the options of the chart or globally...
Chart.plugins.register({
// plugin implementation
});
Which I would like to avoid. Never a fan of global configuration unless absolutely necessary and time-saving.
EDIT2: Sorta gave up on NOT registering it globally, but it still doesn't help a whole lot. I added to my imports...
import { Chart } from 'chart.js';
import * as ChartLabel from 'chartjs-plugin-datalabels';
And then tried to register the plugin in the global Chart.
ngOnInit() {
Chart.plugins.register(ChartLabel);
}
Which has done 'something' as far as I can tell. So I attempted to do a very basic implementation of the plugin. The other tooltips don't work anymore but it only triggers an error when I hover around the bars.
plugins: {
datalabels: {
color: 'white',
display: true,
font: {
weight: 'bold'
},
formatter: Math.round
}
},
No clue what I can do anymore...
Well, since there was literally no answer, I could ultimately settle for the "Global" Chart solution.
Chart.pluginService.register({
// I gave it an id, so I could disable the plugin if needed
id: 'p1',
afterDatasetsDraw: function (chart, _ease) {
let width = chart.chart.width;
let ctx = chart.chart.ctx;
let datasets = chart.data.datasets;
datasets.forEach(function (_dataset, index) {
let meta = chart.getDatasetMeta(index);
// grabbing the array with the data...
let barLabelData = meta.controller._data;
// console.log(meta.controller._data)
if (!meta.hidden) {
meta.data.forEach(function (segment, _index) {
let model = segment._model;
let position = segment.tooltipPosition();
let x = position.x;
let y = position.y;
let height = model.height;
ctx.restore();
ctx.textBaseline = "middle";
// var fontSize = (height / 114).toFixed(2);
ctx.font = 'bold ' + height / 2 + 'px Arial';
ctx.fillStyle = '#777'; //first label's font color
let text = barLabelData[_index];
ctx.fillText(text, x, y);
ctx.save();
})
}
});
}
});
Which ultimately graced me with a sorta okay looking chart. So yeah, it's not at clean as I would like to. But there are still issues with implementation.
My Chart component globally implements my plugin logic. That's awful deisgn here and I have to decouple it. The minor problem is, I have to make sure the labels work always and are properly depicted. But for now I'm pleased it works.
Here's an example done by #paviad:
https://stackblitz.com/edit/ng2-charts-bar-labels
import { Component, OnInit } from '#angular/core';
import { ChartOptions, ChartType, ChartDataSets } from 'chart.js';
import * as pluginDataLabels from 'chartjs-plugin-datalabels';
import { Label } from 'ng2-charts';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public barChartOptions: ChartOptions = {
responsive: true,
// We use these empty structures as placeholders for dynamic theming.
scales: { xAxes: [{}], yAxes: [{}] },
plugins: {
datalabels: {
anchor: 'end',
align: 'end',
font: {
size: 20,
}
}
}
};
...
public barChartPlugins = [pluginDataLabels];
...
Detailed options are here:
https://chartjs-plugin-datalabels.netlify.app/
I'm trying to dynamically add annotations to my high charts in React. I'm using the addAnnotation function to add a new annotation whenever a hover event is triggered on my app, but the annotation does not render. I dropped a debugger into my code, and when I call chart.annotations I can see there is currently an array of annotations, but they are not rendering. I even have make a call to the addPlotLine in this function and the plotline is rendered on the chart. My config file looks like this
chart: {
annotations: [{
labelOptions: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
verticalAlign: 'top',
align: 'right',
}
}],
annotationsOptions: {
},
.... some lots more config options
}
and my on hover function to add the annotation is as follows
if( isNumber(value) )
//this renders a plotline
chart.xAxis[0].addPlotLine(baseChartHandle.generateInteractivePlotLine(value));
// this doesn't render my annotation however
chart.addAnnotation({
linkedTo: value,
title: {
text: "It works!"
}
});
}
I found that when I added annotations using Highcharts that the "linkedTo" function never worked despite my trials.
Despite adding a guid to my point, it never was able to apply it. I would suggest in your case adding it by x and y value, then finding the point that way instead. Here is how I have added annotations in the past successfully:
series.data.forEach(function (point) {
var annotationObject =
{
id: point.id,
labelOptions: {
y: 15,
verticalAlign: 'bottom',
distance: 25
},
labels: []
};
}
var text = <get text you want to push>;
annotationObject.labels.push(
{
point: {
xAxis: 0,
yAxis: 0,
x: point.x,
y: point.y
},
text: text
}
);
_chart.addAnnotation(annotationObject);
});
I also found a bug in HighCharts when using remove annotations, as a heads up. Each point will need an id like above, but it should be called by this line:
_chart.removeAnnotation(id);
It will remove the annotation, however you will get lots of complaints from highcharts if you try to do anything after, and it will break. I found the answer here, in annotations.js :
The red box is code I added. If you do removeAnnotation(ann.id), and it rerenders, it will fail because the labels object is null. Adding this check lets you do that without the labelCollectors failing.
Happy charting.
I draw a plot like this:
var items = $.get("./moonlight_sonata_diameter.data", function(data) {
items = data.split(/\r?\n/).map( pair => pair.split(/\s+/).map(Number) );
$(function () {
plot = $.plot($("#placeholder"),
[ { data: linePoints} ], {
series: {
lines: { show: true }
},
crosshair: { mode: "x" },
grid: { hoverable: true, autoHighlight: false },
yaxis: { min: 0, max: 5 }
});
});
});
Now at a later moment in time, I want to update the crosshair of the plot. However, because it is embedded in so many functions, I don't know how to access it as I am not familiar with jQuery.
Within the script, I can run:
plot.setCrosshair({x: 100})
However, in another script, at another time, there is no object called plot. Is there a way to access it still?
Actually you have put your plot creation code in document ready function and your
plot.setCrosshair({x:100}) is executed just before your plot creation code. so A simple settimeout will do the trick.
just replace
plot.setCrosshair({x: 4})
with
setTimeout(function(){ plot.setCrosshair({x: 41})}, 3000);
and this will work fine. if you call your setCrosshair function after loading the complete dom then you will not need of setTimeout function. I hope this will help and if not then let me know.
Check it at http://plnkr.co/edit/3cMHmzWEIk6c39mblb0Z?p=preview