diff --git a/Modules/QtWidgetsExt/QmitkHistogramJSWidget.cpp b/Modules/QtWidgetsExt/QmitkHistogramJSWidget.cpp index 62ca9a93f6..a03468b6f3 100644 --- a/Modules/QtWidgetsExt/QmitkHistogramJSWidget.cpp +++ b/Modules/QtWidgetsExt/QmitkHistogramJSWidget.cpp @@ -1,249 +1,242 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkHistogramJSWidget.h" #include "mitkPixelTypeMultiplex.h" #include #include #include "mitkRenderingManager.h" #include "mitkBaseRenderer.h" #include "mitkImageTimeSelector.h" #include "mitkExtractSliceFilter.h" #include QmitkHistogramJSWidget::QmitkHistogramJSWidget(QWidget *parent) : QWebView(parent) { // set histogram type to barchart in first instance m_UseLineGraph = false; m_Page = new QmitkJSWebPage(this); setPage(m_Page); // set html from source connect(page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(AddJSObject())); QUrl myUrl = QUrl("qrc:///QtWidgetsExt/Histogram.html"); setUrl(myUrl); // set Scrollbars to be always disabled page()->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); m_ParametricPath = ParametricPathType::New(); } QmitkHistogramJSWidget::~QmitkHistogramJSWidget() { } // adds an Object of Type QmitkHistogramJSWidget to the JavaScript, using QtWebkitBridge void QmitkHistogramJSWidget::AddJSObject() { page()->mainFrame()->addToJavaScriptWindowObject(QString("histogramData"), this); } // reloads WebView, everytime its size has been changed, so the size of the Histogram fits to the size of the widget void QmitkHistogramJSWidget::resizeEvent(QResizeEvent* resizeEvent) { QWebView::resizeEvent(resizeEvent); // workaround for Qt Bug: https://bugs.webkit.org/show_bug.cgi?id=75984 page()->mainFrame()->evaluateJavaScript("disconnectSignals()"); this->reload(); } // method to expose data to JavaScript by using properties void QmitkHistogramJSWidget::ComputeHistogram(HistogramType* histogram) { m_Histogram = histogram; HistogramConstIteratorType startIt = m_Histogram->End(); HistogramConstIteratorType endIt = m_Histogram->End(); HistogramConstIteratorType it = m_Histogram->Begin(); ClearData(); unsigned int i = 0; bool firstValue = false; // removes frequencies of 0, which are outside the first and last bin for (; it != m_Histogram->End(); ++it) { if (it.GetFrequency() > 0.0) { endIt = it; if (!firstValue) { firstValue = true; startIt = it; } } } ++endIt; // generating Lists of measurement and frequencies for (it = startIt ; it != endIt; ++it, ++i) { QVariant frequency = QVariant::fromValue(it.GetFrequency()); QVariant measurement = it.GetMeasurementVector()[0]; m_Frequency.insert(i, frequency); m_Measurement.insert(i, measurement); } - m_Size = m_Histogram->GetSize(0); // we only handle one dimensional histograms - m_IntensityProfile = false; this->SignalDataChanged(); } void QmitkHistogramJSWidget::ClearData() { m_Frequency.clear(); m_Measurement.clear(); - m_Size = 0; } void QmitkHistogramJSWidget::ClearHistogram() { this->ClearData(); this->SignalDataChanged(); } QList QmitkHistogramJSWidget::GetFrequency() { return m_Frequency; } QList QmitkHistogramJSWidget::GetMeasurement() { return m_Measurement; } bool QmitkHistogramJSWidget::GetUseLineGraph() { return m_UseLineGraph; } void QmitkHistogramJSWidget::OnBarRadioButtonSelected() { if (m_UseLineGraph) { m_UseLineGraph = false; this->SignalGraphChanged(); } } void QmitkHistogramJSWidget::OnLineRadioButtonSelected() { if (!m_UseLineGraph) { m_UseLineGraph = true; this->SignalGraphChanged(); } } void QmitkHistogramJSWidget::SetImage(mitk::Image* image) { m_Image = image; } void QmitkHistogramJSWidget::SetPlanarFigure(const mitk::PlanarFigure* planarFigure) { m_PlanarFigure = planarFigure; } template void ReadPixel(mitk::PixelType, mitk::Image::Pointer image, itk::Index<3> indexPoint, double& value) { if (image->GetDimension() == 2) { mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData(0)); itk::Index<2> idx; idx[0] = indexPoint[0]; idx[1] = indexPoint[1]; value = readAccess.GetPixelByIndex(idx); } else if (image->GetDimension() == 3) { mitk::ImagePixelReadAccessor readAccess(image, image->GetVolumeData(0)); itk::Index<3> idx; idx[0] = indexPoint[0]; idx[1] = indexPoint[1]; idx[2] = indexPoint[2]; value = readAccess.GetPixelByIndex(idx); } else { //unhandled } } void QmitkHistogramJSWidget::ComputeIntensityProfile(unsigned int timeStep) { this->ClearData(); m_ParametricPath->Initialize(); if (m_PlanarFigure.IsNull()) { mitkThrow() << "PlanarFigure not set!"; } if (m_Image.IsNull()) { mitkThrow() << "Image not set!"; } mitk::Image::Pointer image; if (m_Image->GetDimension() == 4) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Image); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); image = timeSelector->GetOutput(); } else { image = m_Image; } mitk::IntensityProfile::Pointer intensityProfile = mitk::ComputeIntensityProfile(image, const_cast(m_PlanarFigure.GetPointer())); m_Frequency.clear(); m_Measurement.clear(); int i = -1; mitk::IntensityProfile::ConstIterator end = intensityProfile->End(); for (mitk::IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) { m_Frequency.push_back(it.GetMeasurementVector()[0]); m_Measurement.push_back(++i); } m_IntensityProfile = true; m_UseLineGraph = true; this->SignalDataChanged(); } bool QmitkHistogramJSWidget::GetIntensityProfile() { return m_IntensityProfile; } -double QmitkHistogramJSWidget::GetSize() -{ - return m_Size; -} diff --git a/Modules/QtWidgetsExt/QmitkHistogramJSWidget.h b/Modules/QtWidgetsExt/QmitkHistogramJSWidget.h index b585308c0f..59b6c5023b 100644 --- a/Modules/QtWidgetsExt/QmitkHistogramJSWidget.h +++ b/Modules/QtWidgetsExt/QmitkHistogramJSWidget.h @@ -1,284 +1,283 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QMITKHISTOGRAMJSWIDGET_H #define QMITKHISTOGRAMJSWIDGET_H #include #include #include #include "MitkQtWidgetsExtExports.h" #include #include "mitkImage.h" #include "mitkPlanarFigure.h" #include #include /** * \brief Widget which shows a histogram using JavaScript. * * This class is a QWebView. It shows the histogram for a selected image * or segmentation. It also can display an intensity profile for * path elements, which lais over an image. */ class MitkQtWidgetsExt_EXPORT QmitkHistogramJSWidget : public QWebView { Q_OBJECT /** * \brief Measurement property. * * This property is used in JavaScript as member of the current object. * It holds a QList, containing the measurements of the current histogram. * @see GetMeasurement() */ Q_PROPERTY(QList measurement READ GetMeasurement) /** * \brief Frequency property. * * This property is used in JavaScript as member of the current object. * It holds a QList, containing the frequencies of the current histogram. * @see GetFrequency() */ Q_PROPERTY(QList frequency READ GetFrequency) /** * \brief Line graph property. * * This property is used in JavaScript as member of the current object. * It holds a boolean, which sais wether to use a line or not. * @see GetUseLineGraph() */ Q_PROPERTY(bool useLineGraph READ GetUseLineGraph) /** * @brief intensity profile property. * * This property is used in JavaScript as member of the current object. - * It holds a boolean, which sais wether to use an intensity profile or not. + * It holds a boolean, which says whether to use an intensity profile or not. * @see GetIntensityProfile() */ Q_PROPERTY(bool intensityProfile READ GetIntensityProfile) Q_PROPERTY(double size READ GetSize) public: typedef mitk::Image::HistogramType HistogramType; typedef mitk::Image::HistogramType::ConstIterator HistogramConstIteratorType; typedef itk::PolyLineParametricPath< 3 > ParametricPathType; typedef itk::ParametricPath< 3 >::Superclass PathType; typedef mitk::PlanarFigure::PolyLineType VertexContainerType; explicit QmitkHistogramJSWidget(QWidget *parent = 0); ~QmitkHistogramJSWidget(); /** * \brief Event which notifies a change of the widget size. * * Reimplemented from QWebView::resizeEvent(), * reloads the webframe */ void resizeEvent(QResizeEvent* resizeEvent); /** * \brief Calculates the histogram. * * This function removes all frequencies of 0 until the first bin and behind the last bin. * It writes the measurement and frequency, which are given from the HistogramType, into * m_Measurement and m_Frequency. * The SignalDataChanged is called, to update the information, which is displayed in the webframe. */ void ComputeHistogram(HistogramType* histogram); /** * \brief Calculates the intensityprofile. * * If an image and a pathelement are set, this function * calculates an intensity profile for a pathelement which lies over an image. * Sets m_IntensityProfile and m_UseLineGraph to true. * The SignalDataChanged is called, to update the information, which is displayed in the webframe. */ void ComputeIntensityProfile(unsigned int timeStep = 0); /** * \brief Clears the Histogram. * * This function clears the data and calls SignalDataChanged to update * the displayed information in the webframe. */ void ClearHistogram(); /** * \brief Getter for measurement. * * @return List of measurements. */ QList GetMeasurement(); /** * \brief Getter for frequency. * * @return List of frequencies. */ QList GetFrequency(); /** * \brief Getter for uselineGraph. * * @return True if a linegraph should be used. */ bool GetUseLineGraph(); /** * \brief Getter for intensity profile. * * @return True if current histogram is an intensityprofile */ bool GetIntensityProfile(); double GetSize(); /** * \brief Setter for reference image. * * @param image The corresponding image for an intensity profile. */ void SetImage(mitk::Image* image); /** * \brief Setter for planarFigure. * * @param planarFigure The pathelement for an intensity profile. */ void SetPlanarFigure(const mitk::PlanarFigure* planarFigure); private: /** * \brief List of frequencies. * * A QList which holds the frequencies of the current histogram * or holds the intesities of current intensity profile. */ QList m_Frequency; /** * \brief List of measurements. * * A QList which holds the measurements of the current histogram * or holds the distances of current intensity profile. */ QList m_Measurement; /** * \brief Reference image. * * Holds the image to calculate an intensity profile. */ mitk::Image::Pointer m_Image; /** * \brief Pathelement. * * Holds a not closed planar figure to calculate an intensity profile. */ mitk::PlanarFigure::ConstPointer m_PlanarFigure; bool m_UseLineGraph; bool m_IntensityProfile; - double m_Size; /** * Holds the current histogram */ HistogramType::ConstPointer m_Histogram; /** * Path derived either form user-specified path or from PlanarFigure-generated * path */ PathType::ConstPointer m_DerivedPath; /** * Parametric path as generated from PlanarFigure */ ParametricPathType::Pointer m_ParametricPath; /** * \brief Clears data. * * Clears the QLists m_Measurement and m_Frequency */ void ClearData(); QmitkJSWebPage* m_Page; private slots: /** * \brief Adds an object to JavaScript. * * Adds an object of the widget to JavaScript. * By using this object JavaScript can react to the signals of the widget * and can access the QProperties as members of the object. */ void AddJSObject(); public slots: /** * \brief Slot for radiobutton m_barRadioButton. * * Sets m_UseLineGraph to false. * Calls signal GraphChanged to update the graph in the webframe. */ void OnBarRadioButtonSelected(); /** * \brief Slot for radiobutton m_lineRadioButton. * * Sets m_UseLineGraph to true. * Calls signal GraphChanged to update the graph in the webframe. */ void OnLineRadioButtonSelected(); signals: /** * \brief Signal data has changed. * * It has to be called when the data of the histogram or intensity profile has changed. */ void SignalDataChanged(); /** * \brief Signal graph has changed. * * It has to be called when the graph changed from barchart to linegraph. Vice versa. */ void SignalGraphChanged(); }; #endif diff --git a/Modules/QtWidgetsExt/resources/Histogram.js b/Modules/QtWidgetsExt/resources/Histogram.js index 32b70406d4..07d1751f54 100644 --- a/Modules/QtWidgetsExt/resources/Histogram.js +++ b/Modules/QtWidgetsExt/resources/Histogram.js @@ -1,561 +1,554 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ var margin = { top : 10, bottom : 50, left : 45, right : 20, }; var height = histogramData.height - margin.top - margin.bottom; var width = histogramData.width - margin.left - margin.right; var tension = 0.8; var connected = false; var dur = 1000; var binSize = 10; var min; var max; /* * Connecting signals from qt side with JavaScript methods. */ if (!connected) { connected = true; histogramData.SignalDataChanged.connect(updateHistogram); histogramData.SignalGraphChanged.connect(updateHistogram); } function disconnectSignals() { histogramData.SignalDataChanged.disconnect(updateHistogram); histogramData.SignalGraphChanged.disconnect(updateHistogram); delete histogramData; } /* * Predefinition of scales. */ var xScale = d3.scale.linear() .domain([d3.min(histogramData.measurement)-binSize/2,d3.max(histogramData.measurement)+binSize/2]) .range([0,width]); var yScale = d3.scale.linear() .domain([d3.min(histogramData.frequency),d3.max(histogramData.frequency)]) .range([height,margin.top]); /* * Predefinition of axis elements. */ var xAxis = d3.svg.axis() .scale(xScale) .orient("bottom") .tickFormat(d3.format("s")); var yAxis = d3.svg.axis() .scale(yScale) .orient("left") .tickFormat(d3.format("s")); /* * Predefinition of the zoom. */ var zoombie = d3.behavior.zoom().x(xScale).scaleExtent([1, 50]).on("zoom", zoom); /* * Creation of the svg element, which holds the complete histogram. */ var svg = d3.select("body") .append("svg") .attr("class", "svg") .attr("width", width + margin.right + margin.left) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate (" + margin.left + "," + margin.top + ")") .call(zoombie) .on("mousemove", myMouseMove); /* * Appending a rectangle to the svg, to guarantee the possibility * of zooming on the whole histogram. */ svg.append("rect") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .attr("opacity", 0); /* * Appending a second svg to main svg, which holds only the graph. */ var vis = svg.append("svg") .attr("width", width) .attr("height", height); /* * Predefinition of the lines. */ var line = d3.svg.line() .interpolate("linear") .x(function(d,i) { return xScale(histogramData.measurement[i]-binSize/2); }) .y(function(d) { return yScale(d); }); var linenull = d3.svg.line() .interpolate("linear") .x(function(d,i) { return xScale(histogramData.measurement[i]-binSize/2); }) .y(function(d) { return yScale(0); }); updateHistogram(); /* * Method to update the histogram data * and to change the displayed graph. */ function updateHistogram() { calcBinSize(); if (!histogramData.useLineGraph) { barChart(); } else if (histogramData.useLineGraph) { linePlot() } } /* * Calculation of the bin size. */ function calcBinSize() { - window.alert(histogramData.size); - - binSize = histogramData.size / histogramData.measurement.length; -/* if (1 < histogramData.measurement.length) { min = d3.min(histogramData.measurement); max = d3.max(histogramData.measurement); binSize = ((max - min) / (histogramData.measurement.length-1)); } else { - binsize = 10; + binSize = 10; } -*/ - //window.alert(min); window.alert(max); window.alert(histogramData.measurement.length); - //window.alert(binSize); } /* * Method to display histogram as a barchart. */ function barChart() { definition(); /* * Change zoom to a fixed y-axis. */ zoombie = d3.behavior.zoom().x(xScale).scaleExtent([1, 50]).on("zoom", zoom); svg.call(zoombie); /* * Element to animate transition from linegraph to barchart. */ vis.selectAll("path.line").remove(); vis.selectAll("circle").remove(); /* * Definition of the bar elements. */ var bar = vis.selectAll("rect.bar").data(histogramData.frequency); /* * Definition how to handle new bar elements. */ bar.enter().append("rect") .attr("class", "bar") .on("mouseover", myMouseOver) .on("mouseout", myMouseOut) .attr("x", function(d,i) { return xScale(histogramData.measurement[i]-binSize/2); }) .attr("y", height) .attr("height", 0) .attr("width", barWidth) /* * Definition how to handle changed bar elements. */ bar.transition() .duration(dur) .attr("x", function(d,i) { return xScale(histogramData.measurement[i]-binSize/2); }) .attr("y", myYPostion) .attr("height", barHeight) .attr("width", barWidth); /* * Definition how to handle bar elements which doesn't exist anymore.' */ bar.exit() .transition() .duration(dur) .attr("y", height) .attr("height", 0) .remove(); /* * Update of axis elements. * First delete old ones, then generate new. */ svg.selectAll("g") .remove(); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .call(yAxis); } /* * Method to display histogram as a linegraph. */ function linePlot() { definition(); /* * Change zoom to a zoomable y-axis. */ zoombie = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([1, 50]).on("zoom", zoom); svg.call(zoombie); /* * Elements to animate transitions from barchart to linegraph. * Different transition when an intensity profile is generated. */ if(!histogramData.intensityProfile) { vis.selectAll("rect.bar") .transition() .duration(dur) .attr("height", 0) .remove(); } else { vis.selectAll("rect.bar") .transition() .duration(dur) .attr("y", height) // <-- .attr("height", 0) .remove(); } /* * Creating circle elements, when an intensity profile is generated to show tooltips. * Due performance losses tooltips are not supported for line histograms. */ if(histogramData.intensityProfile) { var circles = vis.selectAll("circle").data(histogramData.frequency); /* * Definition how to handle new circle elements. */ circles.enter() .append("circle") .on("mouseover", myMouseOverLine) .on("mouseout", myMouseOutLine) .attr("cx", function(d,i) { return xScale(histogramData.measurement[i]-binSize/2); }) .attr("cy", function (d) { return yScale(d) }) .attr("r", 5) .attr("opacity", 0) .style("stroke", "red") .style("stroke-width", 1) .style("fill-opacity", 0); /* * Definition how to handle bar elements which doesn't exist anymore. */ circles.exit().remove(); } else { /* * Removing of all circle elements if a line histogram is generated. */ vis.selectAll("circle").remove(); } /* * Creating a new path element. */ var graph = vis.selectAll("path.line") .data([histogramData.frequency]); /* * Definition how to handle a new path element, using predefined lines. */ graph.enter() .append("path") .attr("class", "line") .transition() .duration(dur) .attr("d", line); /* * Definition how to handle change points in an existing path element. */ graph.transition() .duration(dur) .attr("d", line); /* * Update of axis elements. * First delete old ones, then generate new. */ svg.selectAll("g") .remove(); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .call(yAxis); } function definition() { /* * Match scale to current data. */ xScale = d3.scale.linear() .domain([d3.min(histogramData.measurement)-binSize/2,d3.max(histogramData.measurement)+binSize/2]) .range([0,width]); yScale = d3.scale.linear() .domain([d3.min(histogramData.frequency),d3.max(histogramData.frequency)]) .range([height,margin.top]); /* * Match axes to current scale */ xAxis = d3.svg.axis() .scale(xScale) .orient("bottom") .tickFormat(d3.format("s")); yAxis = d3.svg.axis() .scale(yScale) .orient("left") .tickFormat(d3.format("s")); } /* * Method to calculate barwidth in px. */ function barWidth(d, i) { var bw; if (i != (histogramData.measurement.length-1)) { bw =(xScale(histogramData.measurement[i + 1]) - xScale(histogramData.measurement[i])) * (histogramData.frequency.length / (histogramData.frequency.length + 1)) - 1; } else { bw =(xScale(histogramData.measurement[i]) - xScale(histogramData.measurement[i - 1])) * (histogramData.frequency.length / (histogramData.frequency.length + 1)) - 1; } /* * Ensure barwidth is not smaller than 1px. */ bw = bw > 1 ? bw : 1; return bw; } /* * Method to calculate barheight in px. * Ensure barheight is not smaller than 1px. */ function barHeight(d) { var bh; bh = height - yScale(d); bh = bh >=2 ? bh : 2; return bh; } /* * Method to calculate dynamical y positions. */ function myYPostion(d) { var myy = yScale(d); myy = (height-myy) > 2 ? myy : (height-2); if (d == 0) { return height; } return myy; } /* * Method to fit parameters of bars/line when zoomed. * Update axes elements. * Resets the view if scale is 1. */ function zoom() { if (zoombie.scale() == 1) { zoombie.translate([0,0]); xScale.domain([d3.min(histogramData.measurement)-binSize/2,d3.max(histogramData.measurement)+binSize/2]); yScale.domain([d3.min(histogramData.frequency),d3.max(histogramData.frequency)]); } if (!histogramData.useLineGraph) { svg.select(".x.axis").call(xAxis); vis.selectAll(".bar") .attr("width", barWidth) .attr("x", function(d, i) { return xScale(histogramData.measurement[i]-binSize/2); }); } else { svg.select(".x.axis").call(xAxis); svg.select(".y.axis").call(yAxis); vis.selectAll("path.line") .attr("transform", "translate(" + zoombie.translate() + ")scale(" + zoombie.scale() + ")") .style("stroke-width", 1 / zoombie.scale()); vis.selectAll("circle") .attr("cx", function(d, i) { return xScale(histogramData.measurement[i]-binSize/2); }) .attr("cy", function(d) { return yScale(d); }); } } /* * Method to show infobox, while mouse is over a bin. */ function myMouseOver() { var myBar = d3.select(this); var reScale = d3.scale.linear() .domain(xScale.range()) .range(xScale.domain()); var y = myBar.data(); var x = reScale(myBar.attr("x")); myBar.style("fill", "red"); d3.select(".infobox").style("display", "block"); if ((min >= 0) && (max <= 2)) //tooltip for float images { d3.select(".measurement").text("Greayvalue: " + (Math.round(x*1000)/1000)); } else { d3.select(".measurement").text("Greyvalue: " + (Math.round(x*10)/10) + " ... " + (Math.round((x+binSize)*10)/10)); } d3.select(".frequency").text("Frequency: " + y); } /* * Hide infobox, when mouse not over a bin. */ function myMouseOut() { var myBar = d3.select(this); myBar.style("fill", d3.rgb(0,71,185)); d3.select(".infobox").style("display", "none"); } /* * Show tooltip, while mouse is over a circle in an intensity profile. */ function myMouseOverLine() { var myCircle = d3.select(this) var reScale = d3.scale.linear() .domain(xScale.range()) .range(xScale.domain()); var y = myCircle.data(); var x = reScale(myCircle.attr("cx")); x = x >= 0 ? x : 0; myCircle.attr("opacity", 1); d3.select(".infobox").style("display", "block"); d3.select(".measurement").text("Distance: " + (Math.round(x*100)/100) + " mm"); d3.select(".frequency").text("Intensity: " + y); } /* * Hide infobox, when mouse not over a circle. */ function myMouseOutLine() { var myCircle = d3.select(this); myCircle.attr("opacity", 0); d3.select(".infobox").style("display", "none"); } /* * Update mousecoordinates when mouse is moved. * Tooltip is shown on the right side of the mouse until it reaches the right boundary, * the it switches to the left side. */ function myMouseMove() { var infobox = d3.select(".infobox"); var coords = d3.mouse(this); if ((coords[0]+120)<(width-margin.right)) { infobox.style("left", coords[0] + 75 + "px"); infobox.style("top", coords[1] + "px"); } else { infobox.style("left", coords[0] - 90 + "px"); infobox.style("top",coords[1] + "px"); } }