Positioning annotations for negative and positive values on Google Chart

How can I position annotations above for positive values and below for negative ones in a column chart?

Another question regarding value and annotation formatting - how can I achieve the same formatting as vAxis for annotations (values above and below columns)?

https://i.sstatic.net/N6r2Q.png

google.charts.load('current',{callback:drawChart,'packages':['corechart'],'language':'hr'});

function drawChart()
{
  var data = new google.visualization.DataTable();
  data.addColumn('date','Datum');
  data.addColumn('number','Vrijednost');
  data.addColumn('number','Pred. prema preth. 5 dana');
  data.addColumn('number','Pred. prema preth. 10 dana');
  data.addColumn('number','Relativna promjena');
  data.addRows([
  [new Date('2017-08-03'),12.10260,12.09797,12.148753333333,0.3199602122016],
  [new Date('2017-08-02'),12.06400,12.16005,12.176186666667,-0.69882870054079],
  [new Date('2017-08-01'),12.14890,12.12988,12.160606666667,0.3129386508133],
  [new Date('2017-07-31'),12.11100,12.13091,12.14988,-0.001651364026678],
  [new Date('2017-07-28'),12.11120,12.1175,12.116093333333,0.11821210392746],
  [new Date('2017-07-27'),12.09690,12.10942,12.079293333333,0.24113757271416],
  [new Date('2017-07-26'),12.06780,12.10184,12.040733333333,0],
  [new Date('2017-07-25'),12.06780,12.06525,11.992986666667,0.28753781205331],
  [new Date('2017-07-24'),12.03320,12.02595,11.95908,0.18983547592086],
  [new Date('2017-07-21'),12.01040,11.95357,11.932006666667,0.41468798073707],
  [new Date('2017-07-20'),11.96080,11.9183,11.9194,0.1951832460733],
  [new Date('2017-07-19'),11.93750,11.89151,11.914186666667,0.21154604904174],
  [new Date('2017-07-18'),11.91230,11.89439,11.937766666667,0.1235543302851],
  [new Date('2017-07-17'),11.89760,11.93811,11.967046666667,-0.36595680537295],
  [new Date('2017-07-14'),11.94130,11.95136,11.972373333333,0.068716427416171],
  [new Date('2017-07-13'),11.93310,11.96335,11.975713333333,-0.1848567987152],
  [new Date('2017-07-12'),11.95520,11.94968,11.96142,-0.070212979370754],
  [new Date('2017-07-11'),11.96360,11.95871,11.944226666667,0.19429834846403],
  [new Date('2017-07-10'),11.94040,11.9698,11.93224,0.099761076413629],
  [new Date('2017-07-07'),11.92850,11.96977,11.934313333333,-0.13478894228354],
  [new Date('2017-07-06'),11.94460,11.93426,11.931026666667,-0.10036297944233],
  [new Date('2017-07-05'),11.95660,11.86036,11.91198,0.66342251932174],
  [new Date('2017-07-04'),11.87780,11.86771,11.918093333333,0.048011724968622],
  [new Date('2017-07-03'),11.87210,11.88418,11.919446666667,-0.078273604120727],
  [new Date('2017-06-30'),11.88140,11.92094,11.907506666667,-0.076531684958581]
  ]);

  var ColumnOpt = {
    height: 300,
    title: 'Relativna promjena vrijednosti [%]',
    annotations: {textStyle: {fontName: 'Tahoma', fontSize: 9}},
    vAxis: {textStyle: {fontName: 'Tahoma', fontSize: 9}, format: "#.#'%'",
    viewWindow: {min: data.getColumnRange(4).min-0.5}},
    hAxis: {textStyle: {fontName: 'Tahoma', fontSize: 9}, showTextEvery: 5},
    chartArea: {width: '80%', height: '80%'},
    legend: {position: 'none'},
    colors: ['purple']
  };
  
  var view2 = new google.visualization.DataView(data);
  view2.setColumns([0,4,{calc:'stringify',sourceColumn:4,type:'string',role:'annotation'}]);

  var container = document.getElementById('Chart2');
  var chart2=new google.visualization.ColumnChart(container);

  var observer = new MutationObserver(function () {
    $.each($('text[text-anchor="start"]'), function (index, label) {
      var labelValue = parseFloat($(label).text());
      if (labelValue < 0 && $(label).attr('font-height') !== 'bold') {
        var bounds = label.getBBox();
        var chartLayout = container.getChartLayoutInterface();
        $(label).attr('y',chartLayout.getYLocation(labelValue) - bounds.height - 8);
      }
    });
  });
  observer.observe(container,{childList: true,subtree: true});

  chart2.draw(view2,ColumnOpt);
}
<div id="Chart2"></div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://www.gstatic.com/charts/loader.js"></script>

Answer №1

There are several issues to address in this scenario:

On the chart, annotations have the attribute --> text-anchor="middle"
as opposed to text-anchor="start" on the others.

To format the annotations, utilize a number formatter:

var formatAnnotation = new google.visualization.NumberFormat({
  pattern: ColumnOpt.vAxis.format
});

Implement a custom function on the view instead of the predefined "stringify" function:

var view2 = new google.visualization.DataView(data);
view2.setColumns([0,4,{
  calc: function (dt, row) {
    return formatAnnotation.formatValue(dt.getValue(row, 4));
  },
  type: 'string',
  role: 'annotation'
}]);

An issue arises with commas in numbers that need to be replaced with decimals for parseFloat to handle correctly.

Furthermore, there is an overlapping issue with annotations.

This code snippet should help bring you closer to resolving these challenges:

google.charts.load('current',{callback:drawChart,'packages':['corechart'],'language':'hr'});

function drawChart()
{
  // Chart data goes here

  var ColumnOpt = {
    // Chart options and settings
  };

  var formatAnnotation = new google.visualization.NumberFormat({
    pattern: ColumnOpt.vAxis.format
  });

  var view2 = new google.visualization.DataView(data);
  view2.setColumns([0,4,{
    calc: function (dt, row) {
      return formatAnnotation.formatValue(dt.getValue(row, 4));
    },
    type: 'string',
    role: 'annotation'
  }]);
  
  var container = document.getElementById('Chart2');
  var chart2=new google.visualization.ColumnChart(container);

  var observer = new MutationObserver(function () {
    // Observer logic for handling overlapping annotations
  });
  observer.observe(container,{childList: true,subtree: true});

  chart2.draw(view2,ColumnOpt);
}
<div id="Chart2"></div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://www.gstatic.com/charts/loader.js"></script>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Ways to retrieve anchor tag values from an AJAX response without a defined class

if(ajaxRequest.readyState == 4) { var response = ajaxRequest.responseText; response=response.split('^^--^^'); var buname=response[5].split('^^|||^^'); //rest code } The AJAX request has returned the following code, now stored in the va ...

Node-fetch enables dynamic requests

Seeking to retrieve real-time data from a fast-updating API has posed a challenge for me. The issue lies in my code constantly returning the same value. I've experimented with two approaches: var fetch = require("node-fetch"); for(let i=0; i<5; i+ ...

Issue communicating with connect-flash: flash variable is not recognized

I've been diving into books on node.js, express, and mongodb lately. In one of the examples, the author showcases the usage of connect-flash. However, I'm encountering some difficulties getting it to function as expected. Below are snippets from ...

Tips for effectively utilizing vue-draggable (or Sortable JS) within a v-data-table using the powerful combination of Vuetify 2 and Vue JS 2

I'm currently experimenting with integrating vue-draggable into Vuetify's v-data-table based on the instructions provided in this article : https://medium.com/vuetify/drag-n-drop-in-vuetify-part-ii-2b07b4b27684 The article mentions : "The main o ...

Search for a DIV element within iMacro on a consistent basis

I recently started using iMacro and encountered an issue while recording a script that involved clicking on a pop-up when it appeared on the screen. The problem arose because the pop-up only appears when a new event is posted. Therefore, when I initially c ...

Implementing AngularJS directives with jQuery

Utilizing Jquery Selectric to enhance the select box in my AngularJS app led me to create a directive for rendering the element. Below you'll find the directive code along with how it's implemented. The Directive: angular.module('shoeReva ...

Learn how to implement Redux login to display the first letter of a user's name and their login name simultaneously

I am implementing a Redux login feature and after successful login, I want to display the first letter of the user's name or email in a span element within the header section. Can anyone provide a solution for this? Below is my Redux login code snippe ...

Steps to set up Feathers instance in Jest environment once

When running Jest tests, I want to utilize only one instance of feathers "app" throughout. This is how I currently import app in each test: const app = require('../../src/app'); describe(`service`, () => { it('registered the service&ap ...

As soon as I hit the submit button on my website form, the URL is automatically refreshed with the information I provided

I am a beginner when it comes to forms and recently copied and pasted this login snippet code: <div class="login-form-1"> <form id="login-form" class="text-left"> <div class="main-login-form"> <div class="login-group"> ...

Crafting callback functions

My jQuery code looks like this: $('#current_image').fadeOut(function(){ $('#current_image').attr('src',newImage).show(); }); This process is wonderful - the nested function runs smoothly after the fadeOut. I ...

Unable to trigger jQuery .click event

Having recently delved into the world of AJAX, jQuery, and JavaScript, I am facing a challenge that I believe is rooted in a simple oversight on my part. Expressing this issue can be quite perplexing, but I'll do my utmost to articulate it clearly. I ...

Challenges encountered during the updating of nodes in 3D force graphs

I am currently developing an application where users can utilize a dat.gui menu to customize their Three.js animation. Specifically, I am implementing the 3d-force-graph library. Whenever a user makes a change, the following steps are taken: Swap out the ...

Transmit communication from Controller to AJAX script on View page

I am currently utilizing jQuery and AJAX within the View to send data to the Controller for database writing. After a successful submission, a div tag with a green background displaying "OK" text is shown. However, I am interested in implementing a check w ...

Issue with displaying Bootstrap modal on top of another modal

Upon clicking the "Click me here" button, a modal pops up and displays a toast message expressing gratitude. The thank you modal should remain open on top of the save modal even after clicking save. However, upon saving, an error message appears: Cannot r ...

Switching Logo Image when Menu Button is Clicked

I'm looking to adjust the color of my logo when the menu button is clicked to match the overlay style. Currently, I managed to switch from a black logo to a white logo. However, when attempting to close the menu, it doesn't revert back to the bl ...

A recent addition to the webpage is causing the script to malfunction

I am experiencing an issue on my website where the radio button does not trigger the onchange event when I reload content in a DIV using (.load()). How can I solve this problem? $(window).ready(function(){ $('input[name="optionsRadios"]').on ...

The functionality of the "Slots" prop has no impact when used on the material-ui Slider component

Trying to understand the purpose of the "slots" prop in relation to customizing how inner components like track and thumb are rendered within the Slider component. A basic example of rendering a Slider component is shown below const marks = [ { value: 0 ...

What strategies are most effective in managing unexpected drag and drop events while working on a project?

Can you forgive my quirky question? I'm facing a problem with drag/drop events while working on an Electron project. The issue goes like this: During development (using Vue / Electron-builder, etc.), I run in "electron-builder:serve" mode. This mode ...

Managing the React Router component as a variable

I'm currently working on integrating React-Router into an existing React app. Is there a way to use react-router to dynamically display components based on certain conditions? var displayComponent; if(this.state.displayEventComponent){ {/* ...

Combining two arrays without using concatenation, each from separate arrays but sharing one common variable

I'm struggling to combine two arrays in a specific manner and can't quite figure out the correct syntax to achieve this. primaryData = [1,2] secondaryData = [3,4] label = [label1, label2] Currently, I have this working data = $.map(la ...