ASE Home Page Products Download Purchase Support About ASE
ChartDirector Support
Forum HomeForum Home   SearchSearch

Message ListMessage List     Post MessagePost Message

  How to retrieve extra field value within hotspot under mouse?
Posted by John Vella on Nov-23-2023 05:31
I am adding some custom information to a layer using addExtraField. I'm using the extra field to augment the tooltip information shown by way of setHTMLImageMap, like this:

        layer->setHTMLImageMap("","","title='<*cdml*>{dataSetName}<*br*>X = {x}, Y = {value} ENG = {field0}'");

The tooltip part works as expected ("ENG" shows the custom text). I would like to now also access the extra field data in the mouse move handler I've attached to the Qt signal QChartViewer::mouseMovePlotArea.

How would I do this?

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by Peter Kwan on Nov-24-2023 00:01
Hi John,

You can use put the extra field it in the second argument of setHTMLImageMap. The second argument defines what parameters are available in the hot spot handlers.

In the following, I use "{default}&ENG={field0}". It means the available hot spot parameters are the default parameters, plus the ENG parameter equals to {field0}.

layer->setHTMLImageMap("","{default}&ENG={field0}","title='<*cdml*>{dataSetName}<*br*>X = {x}, Y = {value} ENG = {field0}'");

Best Regards
Peter Kwan

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by John Vella on Nov-24-2023 08:57
Thanks Peter for your usual *very* helpful information! However, it's not clear how exactly I would access the extra value "ENG" from within the mouseMovePlotArea handler. For example in my Qt c++ code:

connect(chartViewer, &QChartViewer::mouseMovePlotArea, [this](QMouseEvent *e) {
    auto h = chartViewer->getImageMapHandler();
    if (h) {
        int n = 0;
        while (1) {
            auto val = h->getKey(n++);
            if (!val) break;
            qDebug() << val << h->getValue(val);
        }
    }
});

The output I'm seeing are just the default fields (ie path, coords, title). So how do I retrieve "ENG"'s value for the hotspot under the mouse?

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by Peter Kwan on Nov-24-2023 12:54
Hi John,

Sorry for this problem.

I only tested the code using getHTMLImageMap. It turns out the meaning of {default} is different in setHTMLImageMap and ChartDirector behaves unexpectedly.

The BaseChart.getHTMLImageMap applies to all layers in the chart. For this API, "{default}" refers to the default query string built-into ChartDirector, which can differ depends on chart and layer type.

The Layer.setHTMLImageMap applies only to one layer. For this API, "{default}" refers to the query string in getHTMLImageMap, which applies to all layers. If the query string in getHTMLImageMap is empty, "{default}" will be the empty string (instead of the ChartDirector built-in query string). The "{default}&eng=XXX" will become "&ENG=XXX", which is not the correct syntax.

The solution is to ensure the query string in getHTMLImageMap is not empty. If it is empty, please change it to "{default}" as the query parameter. For example:

viewer->setImageMap(c->getHTMLImageMap("clickable", "{default}", ""));

Another method is not to sure {default} in setHTMLImageMap, but instead specify all parameters you need.

layer->setHTMLImageMap("", "v={value}&n={dataSetName}&ENG={field0}", .....);


Please let me know if this can solve the problem.

Best Regards
Peter Kwan

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by John Vella on Nov-24-2023 22:45
I think we're getting close Peter and the delay to getting what I need is likely my inability to clearly describe what I'm doing and/or what I need. So there are two things that need to occur:

1/ when the layers are created I need to store an extra piece of information("eng" in this case). This is being done this way(note: I'm using "title" since this is also supporting tooltips):

        layer->setHTMLImageMap("","eng={field0}","title='<*cdml*>{dataSetName}<*br*>ENG = {field0}'");


2/ now in the "mouse move" code that I've shown you previously I need to know how to get the value for "eng" for the layer the mouse is currently over and assign that into a std::string.

#2 is where I need further clarification.


I don't have to use QChartViewer::mouseMovePlotArea but I don't know of another way of getting a callback indicating a hotspot has been entered.

thanks for your patience!

-John

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by John Vella on Nov-24-2023 22:51
Another piece of information I just noticed, that in order for my "mouse move" Qt signal to work (it is implemented using chartView->getImageMapHandler) I needed a call like this after all my layers were created:

chartView->setImageMap( c->getHTMLImageMap("") );

Is this above call getting in the way of your proposed solution for me from working?

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by Peter Kwan on Nov-24-2023 23:58
Hi John,

Please try the change the line that calls getHTMLImageMap as follows and check if this can solve the problem:

chartView->setImageMap( c->getHTMLImageMap("", "{default}") );

Best Regards
Peter Kwan

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by Peter Kwan on Nov-25-2023 00:24
Attachments:
Hi John,

In the qtdemo sample code that comes with ChartDirector, there is a Track Box with Floating Legend example.

https://www.advsofteng.com/doc/cdcpp.htm#trackboxqt.htm

I have just modified that example so that instead of the floating legend, it now displays tooltips that include the extra field. In MouseMovePlotArea, it can retrieve the extra field as well as other default parameters.

Best Regards
Peter Kwan
trackbox.cpp
#include <QApplication>
#include <QDebug>
#include "trackbox.h"
#include <vector>
#include <sstream>
#include <algorithm>


TrackBox::TrackBox(QWidget *parent) :
    QDialog(parent)
{
    setWindowTitle("Track Box with Legend");

    // Create the QChartViewer and draw the chart
    m_ChartViewer = new QChartViewer(this);
    drawChart(m_ChartViewer);

    // Set the window to be of the same size as the chart
    setFixedSize(m_ChartViewer->width(), m_ChartViewer->height());

    // Set up the mouseMovePlotArea handler for drawing the track cursor
    connect(m_ChartViewer, SIGNAL(mouseMovePlotArea(QMouseEvent*)),
        SLOT(onMouseMovePlotArea(QMouseEvent*)));
}

TrackBox::~TrackBox()
{
    delete m_ChartViewer->getChart();
}

//
// Draw the chart and display it in the given viewer
//
void TrackBox::drawChart(QChartViewer *viewer)
{
    // The data for the bar chart
    double data0[] = {100, 125, 245, 147, 67};
    double data1[] = {85, 156, 179, 211, 123};
    double data2[] = {97, 87, 56, 267, 157};
    const char *labels[] = {"Mon", "Tue", "Wed", "Thur", "Fri"};
	int noOfPoints = (int)(sizeof(data0)/sizeof(*data0));

    // Create a XYChart object of size 540 x 375 pixels
    XYChart *c = new XYChart(540, 375);

    // Add a title to the chart using 18 pts Times Bold Italic font
    c->addTitle("Average Weekly Network Load", "Times New Roman Bold Italic", 18);

    // Set the plotarea at (50, 55) and of 440 x 280 pixels in size. Use a vertical gradient color
    // from light blue (f9f9ff) to blue (6666ff) as background. Set border and grid lines to white
    // (ffffff).
    c->setPlotArea(50, 55, 440, 280, c->linearGradientColor(0, 55, 0, 335, 0xf9f9ff, 0x6666ff), -1,
        0xffffff, 0xffffff);

    // Add a legend box at (50, 28) using horizontal layout. Use 10pts Arial Bold as font, with
    // transparent background.
	c->addLegend(50, 28, false, "Arial Bold", 10)->setBackground(Chart::Transparent);

    // Set the x axis labels
    c->xAxis()->setLabels(StringArray(labels, noOfPoints));

    // Draw the ticks between label positions (instead of at label positions)
    c->xAxis()->setTickOffset(0.5);

    // Set axis label style to 8pts Arial Bold
    c->xAxis()->setLabelStyle("Arial Bold", 8);
    c->yAxis()->setLabelStyle("Arial Bold", 8);

    // Set axis line width to 2 pixels
    c->xAxis()->setWidth(2);
    c->yAxis()->setWidth(2);

    // Add axis title
    c->yAxis()->setTitle("Throughput (MBytes Per Hour)");

    // Add a multi-bar layer with 3 data sets
	BarLayer *layer = c->addBarLayer(Chart::Side);
    layer->addDataSet(DoubleArray(data0, noOfPoints), 0xff0000, "Server #1");
    layer->addDataSet(DoubleArray(data1, noOfPoints), 0x00ff00, "Server #2");
    layer->addDataSet(DoubleArray(data2, noOfPoints), 0xff8800, "Server #3");   

    // Set bar border to transparent. Use glass lighting effect with light direction from left.
	layer->setBorderColor(Chart::Transparent, Chart::glassEffect(Chart::NormalGlare, Chart::Left));

    // Configure the bars within a group to touch each others (no gap)
	layer->setBarGap(0.2, Chart::TouchBar);

    const char *extraInfo[] = {"AAA", "BBB", "CCC", "DDD", "EEE"};
    layer->addExtraField(StringArray(extraInfo, 5));
    layer->setHTMLImageMap("clickable", "{default}&eng={field0}", "title='<*cdml*>ENG={field0}<*br*>value={value}'");

    // Set the chart image to the QChartViewer
    viewer->setChart(c);
    viewer->setImageMap(c->getHTMLImageMap("clickable", "{default}"));
}

//
// Draw track cursor when mouse is moving over plotarea
//
void TrackBox::onMouseMovePlotArea(QMouseEvent *)
{
   auto h = m_ChartViewer->getImageMapHandler();
   if (h) {
        int n = 0;
        while (1) {
            auto val = h->getKey(n++);
            if (!val) break;
            qDebug() << val << h->getValue(val);
        }
    };
}

//
// Draw the track box with legend
//
void TrackBox::trackBoxLegend(XYChart *c, int mouseX, int mouseY)
{
   // Clear the current dynamic layer and get the DrawArea object to draw on it.
    DrawArea *d = c->initDynamicLayer();

    // The plot area object
    PlotArea *plotArea = c->getPlotArea();

    // Get the data x-value that is nearest to the mouse
    double xValue = c->getNearestXValue(mouseX);

    // Compute the position of the box. This example assumes a label based x-axis, in which the
    // labeling spacing is one x-axis unit. So the left and right sides of the box is 0.5 unit from
    // the central x-value.
    int boxLeft = c->getXCoor(xValue - 0.5);
    int boxRight = c->getXCoor(xValue + 0.5);
    int boxTop = plotArea->getTopY();
    int boxBottom = plotArea->getBottomY();

    // Draw the track box
	d->rect(boxLeft, boxTop, boxRight, boxBottom, 0x000000, Chart::Transparent);

    // Container to hold the legend entries
    std::vector<std::string> legendEntries;

    // Iterate through all layers to build the legend array
    for(int i = 0; i < c->getLayerCount(); ++i) {
        Layer *layer = c->getLayerByZ(i);

        // The data array index of the x-value
        int xIndex = layer->getXIndexOf(xValue);

        // Iterate through all the data sets in the layer
        for(int j = 0; j < layer->getDataSetCount(); ++j) {
            DataSet *dataSet = layer->getDataSetByZ(j);

            // Build the legend entry, consist of the legend icon, the name and the data value.
            double dataValue = dataSet->getValue(xIndex);
			if ((dataValue != Chart::NoValue) && (dataSet->getDataColor() != (int)Chart::Transparent)) {
                std::ostringstream legendEntry;
				legendEntry << dataSet->getLegendIcon() << " " << dataSet->getDataName() << ": " 
					<< c->formatValue(dataValue, "{value|P4}");
				legendEntries.push_back(legendEntry.str());
            }
        }
    }

    // Create the legend by joining the legend entries
    if (legendEntries.size() > 0) {
        std::ostringstream legend;
		legend << "<*block,bgColor=FFFFCC,edgeColor=000000,margin=5*><*font,underline=1*>" << 
			c->xAxis()->getFormattedLabel(xValue) << "<*/font*>";
		for (int i = ((int)legendEntries.size()) - 1; i >= 0; --i)
			legend << "<*br*>" << legendEntries[i];
		legend << "<*/*>";

        // Display the legend at the bottom-right side of the mouse cursor, and make sure the legend
        // will not go outside the chart image.
        TTFText *t = d->text(legend.str().c_str(), "Arial Bold", 8);
        t->draw((std::min)(mouseX + 12, c->getWidth() - t->getWidth()), (std::min)(mouseY + 18,
            c->getHeight() - t->getHeight()), 0x000000, Chart::TopLeft);
		t->destroy();
    }
}

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by John Vella on Nov-25-2023 07:00
The call to getHTMLImageMap needs a non-empty first argument (it can be anything but the empty string, even just a space). So now I'm able to get the added fields via named variables within the mouse move routine ... YAY!

The last question/issue I'm seeing is that specifying a non-empty first argument to getHTMLImageMap causes the mouse pointer to change to the "pointing finger" when over hotspots. I would prefer to avoid that particular pointer since it implies the thing under the mouse responds to clicks (which in my cause they won't).

So how does that pointer get changed or disabled while still allowing the extra fields to be retrieved?

Thanks again Peter for your help and timely replies.

-John

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by Peter Kwan on Nov-25-2023 14:55
Hi John,

There are two methods to disable the "pointing finger" cursor:

(a) Call QWidget::unsetCursor in MouseMovePlotArea. For example:

m_ChartViewer->unsetCursor();

(b) Use "~" as the first parameter in setHTMLImageMap.

Best Regards
Peter Kwan

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by John Vella on Nov-27-2023 22:58
Wow the use of "~" is definitely not something I could get from the documentation on Layer::setHTMLImageMap 😮

Appreciate the "insider" tip 👍

As for how I've chosen to handle detecting when a hotspot has been entered, is there a better way than using QChartViewer::mouseMovePlotArea?

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by John Vella on Nov-30-2023 03:07
I'm sorry to report I still need help retrieving extra values I've assigned to the layer. I wanted to provide more detail on how I'm implementing things.

First I create an empty LineLayer that I will then add datasets to defining the actual lines. Each line/dataset will have a unique value that I want to query for in the hotspot handling code.

After the datasets have been added and I've built up the list of unique values associated with each dataset, I then call Layer::addExtraField with the values list(as a StringArray).

I follow that with this call:

layer->setHTMLImageMap("", "engine={field0}", ...);

And then install the image map like this:

chartView->setImageMap( chart->getHTMLImageMap("{clickable}", "{default}");

NOTE: in the above I have tried changing "field0" to "dsField0".

So what is happening is in the QChartViewer::mouseMovePlotArea handler I _CAN_ see that the "engine" variable exists, but it is _NOT_ getting the correct unique value for the dataset/line at the hotspot.

Please suggest what I need to change to get this working. THANKS in advance.

  Re: How to retrieve extra field value within hotspot under mouse?
Posted by Peter Kwan on Nov-30-2023 17:27
Hi John,

The code should be like:


Layer *layer = c.addLineLayer(.....);
layer->addDataSet(.....);
layer->addDataSet(.....);
layer->addDataSet(.....);

// One StringArray for each data set
layer->addExtraField(....);
layer->addExtraField(....);
layer->addExtraField(....);

Then use engine={dsdiField0} to reference the extra field.

Best Regards
Peter Kwan