highcharts / highcharts-ios

iOS wrapper for Highcharts.
Other
127 stars 39 forks source link

IOS swift using HighChart Library how to draw scatter circle with convert point values to pixels? #441

Closed ivishalsingh1986 closed 5 months ago

ivishalsingh1986 commented 5 months ago

In iOS swift I used Scatter graph to draw circle, to get the radius of circle I have two points min and max on xAxis, radius = (maxPoint - minPoint)/2, but it plot very small circle

             let strategyMarker = HIMarker()
    strategyMarker.radius = (maxPoint - minPoint)/2
    strategyMarker.symbol = "circle"

In attached graph radius = (22 - 9) / 2 = 6 but its plotting very small graphs, How can convert this pointValue to pixelValue to touch this circle to vertical line, please suggest.

Screenshot 2024-06-04 at 12 09 07 PM

MikolajMichalczak commented 5 months ago

Hi @ivishalsingh1986! We are looking into this matter. Thank you for your patience. I will let you know as soon as I have more information. Could you tell me how do you get this maxPoint and minPoint?

ivishalsingh1986 commented 5 months ago

Hi @MikolajMichalczak Thanks for looking into it. maxPoint and minPoint are dynamic values and can varies, we draw the HIPolotLines() on these points on xAxis, We gets one more point Z between maxPoint and minPoint, Its our requirement to draw the circle considering the Z point as center of the circle and it should touch the maxPoint / minPoint which ever is nearest or touch the both if its in exact center of both points, so we can calculate the distance on xAxis and using as radius of the circle but the circle is not plot as expecting radius on graph. Please let me know if you need any other details.

MikolajMichalczak commented 5 months ago

@ivishalsingh1986, thank you for the detailed explanation.

First of all, scatter.marker.radius is specified in pixels, so in your example, a 6 px radius is indeed small. As you noticed, there is no native method for mobile wrappers like toPixels(). Therefore, I suggest utilizing HIFunction with a JavaScript function passed as a string.

Let's use the following chart as an example:

let options = HIOptions()

let minPoint = 180 as NSNumber
let maxPoint = 190 as NSNumber

let chart = HIChart()
chart.type = "scatter"
options.chart = chart

let xAxis = HIXAxis()
xAxis.title = HITitle()
xAxis.title.text = "Height (cm)"

let yAxis = HIYAxis()
yAxis.title = HITitle()
yAxis.title.text = "Weight (kg)"
options.yAxis = [yAxis]

let plotLines = HIPlotLines()
plotLines.width = 2
plotLines.value = minPoint

let plotLines2 = HIPlotLines()
plotLines2.width = 2
plotLines2.value = maxPoint

xAxis.plotLines = [plotLines, plotLines2]

options.xAxis = [xAxis]

let series = HIScatter()
series.color = HIColor(name: "rgba(223, 83, 83, .5)")
series.data = [[175.0, 65.6], [195.3, 71.8], [193.5, 80.7], [185, 72.6], [187.2, 78.8]]

options.series = [series]

As we can see, there is one point with an x value of 185 and two HIPlotLines() at minPoint (180) and maxPoint (190):

Next, we can use the following JavaScript, where we select a specific point (or iterate over all points if needed) and set its marker radius to half the distance between minPoint and maxPoint converted to pixels:

function() {
    const chart = this;
    const point = chart.series[0].points[3];
    const pxMin = chart.xAxis[0].toPixels(minPoint, true);
    const pxMax = chart.xAxis[0].toPixels(maxPoint, true);
    const pxDistance = pxMax - pxMin;

    point.update({
        marker: {
            enabled: true,
            radius: pxDistance / 2
        }
    });
}

We need to add this function to the load event:

let options = HIOptions()

let minPoint = 180 as NSNumber
let maxPoint = 190 as NSNumber

let chart = HIChart()
chart.type = "scatter"
options.chart = chart

chart.events = HIEvents()
chart.events.load = HIFunction(jsFunction: "function() { const chart = this; const point = chart.series[0].points[3]; var pxMin = chart.xAxis[0].toPixels(\(minPoint), true); var pxMax = chart.xAxis[0].toPixels(\(maxPoint), true); var pxDistance = pxMax - pxMin; point.update({ marker: { enabled: true, radius: pxDistance/2 } }); }")
ivishalsingh1986 commented 5 months ago

Hi @MikolajMichalczak , Thanks a lot for your support, Your given solution is worked fine as per our need. Thanks!

I have a related question, As we draw a big circle if another smaller circle comes inside that big one I need to change the color of small circle if its outside the big circle it should be different color. I tried using the C1C2 = sqrt((x1 – x2)2 + (y1 – y2)2) formula with in the HIFunction bit it did't work out as I think X and Y axis have different pixel values.

Please suggest how can we achieve this using the same graph, Is HIchart using scatter graph API itself have any inbuilt function to determine this.

Thanks :)

MikolajMichalczak commented 5 months ago

@ivishalsingh1986 Do you mean when it is fully inside or just when it intersects?

ivishalsingh1986 commented 5 months ago

@MikolajMichalczak I mean fully inside

MikolajMichalczak commented 5 months ago

@ivishalsingh1986 First of all, when one marker covers another, there is a slight distinction between their colors (I mean the intensity of the color). Therefore, if you want completely different colors, you have to implement it by yourself. Basically, you need to convert the coordinates of both points to pixels and then check if a circle lies inside another using the formula (distance + radiusSmall <= radiusBig). The JavaScript code could look as follows:

function() {
    const chart = this;
    const pointBig = chart.series[0].points[3];
    const pxMin = chart.xAxis[0].toPixels(\(minPoint), true);
    const pxMax = chart.xAxis[0].toPixels(\(maxPoint), true);
    const pxDistance = pxMax - pxMin;
    const radiusBig = pxDistance / 2;
    const radiusSmall = 20;
    pointBig.update({
        marker: {
            enabled: true,
            radius: radiusBig
        }
    });
    const pointSmall = chart.series[0].points[4];
    const pointSmallXPx = chart.xAxis[0].toPixels(pointSmall.x, true);
    const pointSmallYPx = chart.yAxis[0].toPixels(pointSmall.y, true);
    const pointBigXPx = chart.xAxis[0].toPixels(pointBig.x, true);
    const pointBigYPx = chart.yAxis[0].toPixels(pointBig.y, true);
    const markersDist = parseInt(Math.sqrt(((pointSmallXPx - pointBigXPx) * (pointSmallXPx - pointBigXPx)) + ((pointSmallYPx - pointBigYPx) * (pointSmallYPx - pointBigYPx))));
    if (markersDist + radiusSmall <= radiusBig) {
        pointSmall.update({
            marker: {
                fillColor: 'blue'
            }
        });
    };
}

The iOS code will look like this:

let options = HIOptions()

      let minPoint = 180 as NSNumber
      let maxPoint = 190 as NSNumber

      let chart = HIChart()
      chart.type = "scatter"
      options.chart = chart

      chart.events = HIEvents()
      chart.events.load = HIFunction(jsFunction: "function() {const chart = this; const pointBig = chart.series[0].points[3]; const pxMin = chart.xAxis[0].toPixels(\(minPoint), true);const pxMax = chart.xAxis[0].toPixels(\(maxPoint), true);const pxDistance = pxMax - pxMin;const radiusBig = pxDistance / 2; const radiusSmall = 20; pointBig.update({marker: {enabled: true,radius: radiusBig}}); const pointSmall = chart.series[0].points[4]; const pointSmallXPx = chart.xAxis[0].toPixels(pointSmall.x, true); const pointSmallYPx = chart.yAxis[0].toPixels(pointSmall.y, true); const pointBigXPx = chart.xAxis[0].toPixels(pointBig.x, true); const pointBigYPx = chart.yAxis[0].toPixels(pointBig.y, true); const markersDist = parseInt(Math.sqrt(((pointSmallXPx - pointBigXPx) * (pointSmallXPx - pointBigXPx)) + ((pointSmallYPx - pointBigYPx)  * (pointSmallYPx - pointBigYPx)))); if (markersDist + radiusSmall <= radiusBig) { pointSmall.update({ marker: { fillColor: 'blue'} });};}")

      let xAxis = HIXAxis()
      xAxis.title = HITitle()
      xAxis.title.text = "Height (cm)"

      let yAxis = HIYAxis()
      yAxis.title = HITitle()
      yAxis.title.text = "Weight (kg)"
      options.yAxis = [yAxis]

      let plotLines = HIPlotLines()
      plotLines.width = 2
      plotLines.value = minPoint

      let plotLines2 = HIPlotLines()
      plotLines2.width = 2
      plotLines2.value = maxPoint

      xAxis.plotLines = [plotLines, plotLines2]

      options.xAxis = [xAxis]

      let series = HIScatter()
      let marker = HIMarker()
      marker.radius = 20
      series.marker = marker
      series.color = HIColor(name: "rgba(223, 83, 83, .5)")
      series.data = [[175.0, 65.6], [195.3, 71.8], [193.5, 80.7], [185, 72.6], [187.2, 72.8]]

      options.series = [series]

image

I simply changed one point's coordinates to be inside another marker and retrieved them by indexes statically. If you would like to make it more dynamic, you have to iterate over all points and enhance it by yourself.

ivishalsingh1986 commented 5 months ago

Hi @MikolajMichalczak Thanks a lot for your support :), Your given solution is worked fine as per our need. Thanks...