628 }
629 }
630
631 /**
632 * Called during layout if the tickmarks have been updated, allowing subclasses to do anything they need to
633 * in reaction.
634 */
635 protected void tickMarksUpdated(){}
636
637 /**
638 * Invoked during the layout pass to layout this axis and all its content.
639 */
640 @Override protected void layoutChildren() {
641 final double width = getWidth();
642 final double height = getHeight();
643 final double tickMarkLength = (isTickMarkVisible() && getTickLength() > 0) ? getTickLength() : 0;
644 final boolean isFirstPass = oldLength == 0;
645 // auto range if it is not valid
646 final Side side = getEffectiveSide();
647 final double length = (side.isVertical()) ? height : width;
648 int tickIndex = 0;
649 boolean rangeInvalid = !isRangeValid();
650 boolean lengthDiffers = oldLength != length;
651 if (lengthDiffers || rangeInvalid) {
652 // get range
653 Object range;
654 if(isAutoRanging()) {
655 // auto range
656 range = autoRange(length);
657 // set current range to new range
658 setRange(range, getAnimated() && !isFirstPass && impl_isTreeVisible() && rangeInvalid);
659 } else {
660 range = getRange();
661 }
662 // calculate new tick marks
663 List<T> newTickValues = calculateTickValues(length, range);
664
665 // remove everything
666 Iterator<TickMark<T>> tickMarkIterator = tickMarks.iterator();
667 while (tickMarkIterator.hasNext()) {
668 TickMark<T> tick = tickMarkIterator.next();
703 // call tick marks updated to inform subclasses that we have updated tick marks
704 tickMarksUpdated();
705 // mark all done
706 oldLength = length;
707 rangeValid = true;
708 }
709
710 if (lengthDiffers || rangeInvalid || measureInvalid || tickLabelsVisibleInvalid) {
711 measureInvalid = false;
712 tickLabelsVisibleInvalid = false;
713 // RT-12272 : tick labels overlapping
714 labelsToSkip.clear();
715 double prevEnd = -Double.MAX_VALUE;
716 double lastStart = Double.MAX_VALUE;
717 switch (side) {
718 case LEFT:
719 case RIGHT:
720 int stop = 0;
721 for (; stop < tickMarks.size(); ++stop) {
722 TickMark<T> m = tickMarks.get(stop);
723 if (m.isTextVisible()) {
724 double tickHeight = measureTickMarkSize(m.getValue(), getRange()).getHeight();
725 lastStart = updateAndGetDisplayPosition(m) - tickHeight / 2;
726 break;
727 } else {
728 labelsToSkip.set(stop);
729 }
730 }
731
732 for (int i = tickMarks.size() - 1; i > stop; i--) {
733 TickMark<T> m = tickMarks.get(i);
734 if (!m.isTextVisible()) {
735 labelsToSkip.set(i);
736 continue;
737 }
738 double tickHeight = measureTickMarkSize(m.getValue(), getRange()).getHeight();
739 double tickStart = updateAndGetDisplayPosition(m) - tickHeight / 2;
740 if (tickStart <= prevEnd || tickStart + tickHeight > lastStart) {
741 labelsToSkip.set(i);
742 } else {
743 prevEnd = tickStart + tickHeight;
744 }
745 }
746 break;
747 case BOTTOM:
748 case TOP:
749 stop = tickMarks.size() - 1;
750 for (; stop >= 0; --stop) {
751 TickMark<T> m = tickMarks.get(stop);
752 if (m.isTextVisible()) {
753 double tickWidth = measureTickMarkSize(m.getValue(), getRange()).getWidth();
754 lastStart = updateAndGetDisplayPosition(m) - tickWidth / 2;
755 break;
756 } else {
757 labelsToSkip.set(stop);
758 }
759 }
760
761 for (int i = 0; i < stop; ++i) {
762 TickMark<T> m = tickMarks.get(i);
763 if (!m.isTextVisible()) {
764 labelsToSkip.set(i);
765 continue;
766 }
767 double tickWidth = measureTickMarkSize(m.getValue(), getRange()).getWidth();
768 double tickStart = updateAndGetDisplayPosition(m) - tickWidth / 2;
769 if (tickStart <= prevEnd || tickStart + tickWidth > lastStart) {
770 labelsToSkip.set(i);
771 } else {
772 prevEnd = tickStart + tickWidth;
773 }
774 }
775 break;
776 }
777 }
778
779 // clear tick mark path elements as we will recreate
780 tickMarkPath.getElements().clear();
781 // do layout of axis label, tick mark lines and text
782 double effectiveLabelRotation = getEffectiveTickLabelRotation();
783 if (Side.LEFT.equals(side)) {
784 // offset path to make strokes snap to pixel
785 tickMarkPath.setLayoutX(-0.5);
786 tickMarkPath.setLayoutY(0.5);
787 if (getLabel() != null) {
788 axisLabel.getTransforms().setAll(new Translate(0, height), new Rotate(-90, 0, 0));
789 axisLabel.setLayoutX(0);
790 axisLabel.setLayoutY(0);
791 //noinspection SuspiciousNameCombination
792 axisLabel.resize(height, Math.ceil(axisLabel.prefHeight(width)));
793 }
794 tickIndex = 0;
795 for (int i = 0; i < tickMarks.size(); i++) {
796 TickMark<T> tick = tickMarks.get(i);
797 positionTextNode(tick.textNode, width - getTickLabelGap() - tickMarkLength,
798 tick.getPosition(), effectiveLabelRotation, side);
799
800 // check if position is inside bounds
801 if (tick.getPosition() >= 0 && tick.getPosition() <= Math.ceil(length)) {
802 if (isTickLabelsVisible()) {
803 tick.textNode.setVisible(!labelsToSkip.get(i));
804 tickIndex++;
805 }
806 // add tick mark line
807 tickMarkPath.getElements().addAll(
808 new MoveTo(width - tickMarkLength, tick.getPosition()),
809 new LineTo(width, tick.getPosition())
810 );
811 } else {
812 tick.textNode.setVisible(false);
813 }
814 }
815 } else if (Side.RIGHT.equals(side)) {
816 // offset path to make strokes snap to pixel
817 tickMarkPath.setLayoutX(0.5);
818 tickMarkPath.setLayoutY(0.5);
819 tickIndex = 0;
820 for (int i = 0; i < tickMarks.size(); i++) {
821 TickMark<T> tick = tickMarks.get(i);
822 positionTextNode(tick.textNode, getTickLabelGap() + tickMarkLength,
823 tick.getPosition(), effectiveLabelRotation, side);
824 // check if position is inside bounds
825 if (tick.getPosition() >= 0 && tick.getPosition() <= Math.ceil(length)) {
826 if (isTickLabelsVisible()) {
827 tick.textNode.setVisible(!labelsToSkip.get(i));
828 tickIndex++;
829 }
830 // add tick mark line
831 tickMarkPath.getElements().addAll(
832 new MoveTo(0, tick.getPosition()),
833 new LineTo(tickMarkLength, tick.getPosition())
834 );
835 } else {
836 tick.textNode.setVisible(false);
837 }
838 }
839 if (getLabel() != null) {
840 final double axisLabelWidth = Math.ceil(axisLabel.prefHeight(width));
841 axisLabel.getTransforms().setAll(new Translate(0, height), new Rotate(-90, 0, 0));
842 axisLabel.setLayoutX(width-axisLabelWidth);
843 axisLabel.setLayoutY(0);
844 //noinspection SuspiciousNameCombination
845 axisLabel.resize(height, axisLabelWidth);
846 }
847 } else if (Side.TOP.equals(side)) {
848 // offset path to make strokes snap to pixel
849 tickMarkPath.setLayoutX(0.5);
850 tickMarkPath.setLayoutY(-0.5);
851 if (getLabel() != null) {
852 axisLabel.getTransforms().clear();
853 axisLabel.setLayoutX(0);
854 axisLabel.setLayoutY(0);
855 axisLabel.resize(width, Math.ceil(axisLabel.prefHeight(width)));
856 }
857 tickIndex = 0;
858 for (int i = 0; i < tickMarks.size(); i++) {
859 TickMark<T> tick = tickMarks.get(i);
860 positionTextNode(tick.textNode, tick.getPosition(), height - tickMarkLength - getTickLabelGap(),
861 effectiveLabelRotation, side);
862 // check if position is inside bounds
863 if (tick.getPosition() >= 0 && tick.getPosition() <= Math.ceil(length)) {
864 if (isTickLabelsVisible()) {
865 tick.textNode.setVisible(!labelsToSkip.get(i));
866 tickIndex++;
867 }
868 // add tick mark line
869 tickMarkPath.getElements().addAll(
870 new MoveTo(tick.getPosition(), height),
871 new LineTo(tick.getPosition(), height - tickMarkLength)
872 );
873 } else {
874 tick.textNode.setVisible(false);
875 }
876 }
877 } else {
878 // BOTTOM
879 // offset path to make strokes snap to pixel
880 tickMarkPath.setLayoutX(0.5);
881 tickMarkPath.setLayoutY(0.5);
882 tickIndex = 0;
883 for (int i = 0; i < tickMarks.size(); i++) {
884 TickMark<T> tick = tickMarks.get(i);
885 final double xPos = Math.round(getDisplayPosition(tick.getValue()));
886 // System.out.println("tick pos at : "+tickIndex+" = "+xPos);
887 positionTextNode(tick.textNode, xPos, tickMarkLength + getTickLabelGap(),
888 effectiveLabelRotation, side);
889 // check if position is inside bounds
890 if (xPos >= 0 && xPos <= Math.ceil(length)) {
891 if (isTickLabelsVisible()) {
892 tick.textNode.setVisible(!labelsToSkip.get(i));
893 tickIndex++;
894 }
895 // add tick mark line
896 tickMarkPath.getElements().addAll(
897 new MoveTo(xPos, 0),
898 new LineTo(xPos, tickMarkLength)
899 );
900 } else {
901 tick.textNode.setVisible(false);
902 }
903 }
904 if (getLabel() != null) {
905 axisLabel.getTransforms().clear();
906 final double labelHeight = Math.ceil(axisLabel.prefHeight(width));
907 axisLabel.setLayoutX(0);
908 axisLabel.setLayoutY(height-labelHeight);
909 axisLabel.resize(width, labelHeight);
910 }
911 }
912 }
913
914 private double updateAndGetDisplayPosition(TickMark<T> m) {
915 double displayPosition = getDisplayPosition(m.getValue());
916 m.setPosition(displayPosition);
917 return displayPosition;
918 }
919
920 /**
921 * Positions a text node to one side of the given point, it X height is vertically centered on point if LEFT or
922 * RIGHT and its centered horizontally if TOP ot BOTTOM.
923 *
924 * @param node The text node to position
925 * @param posX The x position, to place text next to
926 * @param posY The y position, to place text next to
927 * @param angle The text rotation
928 * @param side The side to place text next to position x,y at
929 */
930 private void positionTextNode(Text node, double posX, double posY, double angle, Side side) {
931 node.setLayoutX(0);
932 node.setLayoutY(0);
933 node.setRotate(angle);
934 final Bounds bounds = node.getBoundsInParent();
935 if (Side.LEFT.equals(side)) {
936 node.setLayoutX(posX-bounds.getWidth()-bounds.getMinX());
937 node.setLayoutY(posY - (bounds.getHeight() / 2d) - bounds.getMinY());
938 } else if (Side.RIGHT.equals(side)) {
939 node.setLayoutX(posX-bounds.getMinX());
940 node.setLayoutY(posY-(bounds.getHeight()/2d)-bounds.getMinY());
941 } else if (Side.TOP.equals(side)) {
942 node.setLayoutX(posX-(bounds.getWidth()/2d)-bounds.getMinX());
943 node.setLayoutY(posY-bounds.getHeight()-bounds.getMinY());
944 } else {
945 node.setLayoutX(posX-(bounds.getWidth()/2d)-bounds.getMinX());
946 node.setLayoutY(posY-bounds.getMinY());
947 }
948 }
949
950 /**
951 * Get the string label name for a tick mark with the given value
952 *
953 * @param value The value to format into a tick label string
954 * @return A formatted string for the given value
955 */
956 protected abstract String getTickMarkLabel(T value);
957
958 /**
959 * Measure the size of the label for given tick mark value. This uses the font that is set for the tick marks
960 *
961 *
962 * @param labelText tick mark label text
963 * @param rotation The text rotation
964 * @return size of tick mark label for given value
965 */
966 protected final Dimension2D measureTickMarkLabelSize(String labelText, double rotation) {
967 measure.setRotate(rotation);
968 measure.setText(labelText);
969 Bounds bounds = measure.getBoundsInParent();
|
628 }
629 }
630
631 /**
632 * Called during layout if the tickmarks have been updated, allowing subclasses to do anything they need to
633 * in reaction.
634 */
635 protected void tickMarksUpdated(){}
636
637 /**
638 * Invoked during the layout pass to layout this axis and all its content.
639 */
640 @Override protected void layoutChildren() {
641 final double width = getWidth();
642 final double height = getHeight();
643 final double tickMarkLength = (isTickMarkVisible() && getTickLength() > 0) ? getTickLength() : 0;
644 final boolean isFirstPass = oldLength == 0;
645 // auto range if it is not valid
646 final Side side = getEffectiveSide();
647 final double length = (side.isVertical()) ? height : width;
648 boolean rangeInvalid = !isRangeValid();
649 boolean lengthDiffers = oldLength != length;
650 if (lengthDiffers || rangeInvalid) {
651 // get range
652 Object range;
653 if(isAutoRanging()) {
654 // auto range
655 range = autoRange(length);
656 // set current range to new range
657 setRange(range, getAnimated() && !isFirstPass && impl_isTreeVisible() && rangeInvalid);
658 } else {
659 range = getRange();
660 }
661 // calculate new tick marks
662 List<T> newTickValues = calculateTickValues(length, range);
663
664 // remove everything
665 Iterator<TickMark<T>> tickMarkIterator = tickMarks.iterator();
666 while (tickMarkIterator.hasNext()) {
667 TickMark<T> tick = tickMarkIterator.next();
702 // call tick marks updated to inform subclasses that we have updated tick marks
703 tickMarksUpdated();
704 // mark all done
705 oldLength = length;
706 rangeValid = true;
707 }
708
709 if (lengthDiffers || rangeInvalid || measureInvalid || tickLabelsVisibleInvalid) {
710 measureInvalid = false;
711 tickLabelsVisibleInvalid = false;
712 // RT-12272 : tick labels overlapping
713 labelsToSkip.clear();
714 double prevEnd = -Double.MAX_VALUE;
715 double lastStart = Double.MAX_VALUE;
716 switch (side) {
717 case LEFT:
718 case RIGHT:
719 int stop = 0;
720 for (; stop < tickMarks.size(); ++stop) {
721 TickMark<T> m = tickMarks.get(stop);
722 double tickPosition = updateAndGetDisplayPosition(m);
723 if (m.isTextVisible()) {
724 double tickHeight = measureTickMarkSize(m.getValue(), getRange()).getHeight();
725 lastStart = tickPosition - tickHeight / 2;
726 break;
727 } else {
728 labelsToSkip.set(stop);
729 }
730 }
731
732 for (int i = tickMarks.size() - 1; i > stop; i--) {
733 TickMark<T> m = tickMarks.get(i);
734 double tickPosition = updateAndGetDisplayPosition(m);
735 if (!m.isTextVisible()) {
736 labelsToSkip.set(i);
737 continue;
738 }
739 double tickHeight = measureTickMarkSize(m.getValue(), getRange()).getHeight();
740 double tickStart = tickPosition - tickHeight / 2;
741 if (tickStart <= prevEnd || tickStart + tickHeight > lastStart) {
742 labelsToSkip.set(i);
743 } else {
744 prevEnd = tickStart + tickHeight;
745 }
746 }
747 break;
748 case BOTTOM:
749 case TOP:
750 stop = tickMarks.size() - 1;
751 for (; stop >= 0; --stop) {
752 TickMark<T> m = tickMarks.get(stop);
753 double tickPosition = updateAndGetDisplayPosition(m);
754 if (m.isTextVisible()) {
755 double tickWidth = measureTickMarkSize(m.getValue(), getRange()).getWidth();
756 lastStart = tickPosition - tickWidth / 2;
757 break;
758 } else {
759 labelsToSkip.set(stop);
760 }
761 }
762
763 for (int i = 0; i < stop; ++i) {
764 TickMark<T> m = tickMarks.get(i);
765 double tickPosition = updateAndGetDisplayPosition(m);
766 if (!m.isTextVisible()) {
767 labelsToSkip.set(i);
768 continue;
769 }
770 double tickWidth = measureTickMarkSize(m.getValue(), getRange()).getWidth();
771 double tickStart = tickPosition - tickWidth / 2;
772 if (tickStart <= prevEnd || tickStart + tickWidth > lastStart) {
773 labelsToSkip.set(i);
774 } else {
775 prevEnd = tickStart + tickWidth;
776 }
777 }
778 break;
779 }
780 }
781
782 // clear tick mark path elements as we will recreate
783 tickMarkPath.getElements().clear();
784 // do layout of axis label, tick mark lines and text
785 double effectiveLabelRotation = getEffectiveTickLabelRotation();
786 if (Side.LEFT.equals(side)) {
787 // offset path to make strokes snap to pixel
788 tickMarkPath.setLayoutX(-0.5);
789 tickMarkPath.setLayoutY(0.5);
790 if (getLabel() != null) {
791 axisLabel.getTransforms().setAll(new Translate(0, height), new Rotate(-90, 0, 0));
792 axisLabel.setLayoutX(0);
793 axisLabel.setLayoutY(0);
794 //noinspection SuspiciousNameCombination
795 axisLabel.resize(height, Math.ceil(axisLabel.prefHeight(width)));
796 }
797 for (int i = 0; i < tickMarks.size(); i++) {
798 TickMark<T> tick = tickMarks.get(i);
799 positionTextNode(tick.textNode, width - getTickLabelGap() - tickMarkLength,
800 tick.getPosition(), effectiveLabelRotation, side);
801 updateTickMark(tick, i, length,
802 width - tickMarkLength, tick.getPosition(),
803 width, tick.getPosition());
804 }
805 } else if (Side.RIGHT.equals(side)) {
806 // offset path to make strokes snap to pixel
807 tickMarkPath.setLayoutX(0.5);
808 tickMarkPath.setLayoutY(0.5);
809 if (getLabel() != null) {
810 final double axisLabelWidth = Math.ceil(axisLabel.prefHeight(width));
811 axisLabel.getTransforms().setAll(new Translate(0, height), new Rotate(-90, 0, 0));
812 axisLabel.setLayoutX(width-axisLabelWidth);
813 axisLabel.setLayoutY(0);
814 //noinspection SuspiciousNameCombination
815 axisLabel.resize(height, axisLabelWidth);
816 }
817 for (int i = 0; i < tickMarks.size(); i++) {
818 TickMark<T> tick = tickMarks.get(i);
819 positionTextNode(tick.textNode, getTickLabelGap() + tickMarkLength,
820 tick.getPosition(), effectiveLabelRotation, side);
821 updateTickMark(tick, i, length,
822 0, tick.getPosition(),
823 tickMarkLength, tick.getPosition());
824 }
825 } else if (Side.TOP.equals(side)) {
826 // offset path to make strokes snap to pixel
827 tickMarkPath.setLayoutX(0.5);
828 tickMarkPath.setLayoutY(-0.5);
829 if (getLabel() != null) {
830 axisLabel.getTransforms().clear();
831 axisLabel.setLayoutX(0);
832 axisLabel.setLayoutY(0);
833 axisLabel.resize(width, Math.ceil(axisLabel.prefHeight(width)));
834 }
835 for (int i = 0; i < tickMarks.size(); i++) {
836 TickMark<T> tick = tickMarks.get(i);
837 positionTextNode(tick.textNode, tick.getPosition(), height - tickMarkLength - getTickLabelGap(),
838 effectiveLabelRotation, side);
839 updateTickMark(tick, i, length,
840 tick.getPosition(), height,
841 tick.getPosition(), height - tickMarkLength);
842 }
843 } else {
844 // BOTTOM
845 // offset path to make strokes snap to pixel
846 tickMarkPath.setLayoutX(0.5);
847 tickMarkPath.setLayoutY(0.5);
848 if (getLabel() != null) {
849 axisLabel.getTransforms().clear();
850 final double labelHeight = Math.ceil(axisLabel.prefHeight(width));
851 axisLabel.setLayoutX(0);
852 axisLabel.setLayoutY(height - labelHeight);
853 axisLabel.resize(width, labelHeight);
854 }
855 for (int i = 0; i < tickMarks.size(); i++) {
856 TickMark<T> tick = tickMarks.get(i);
857 positionTextNode(tick.textNode, tick.getPosition(), tickMarkLength + getTickLabelGap(),
858 effectiveLabelRotation, side);
859 updateTickMark(tick, i, length,
860 tick.getPosition(), 0,
861 tick.getPosition(), tickMarkLength);
862 }
863 }
864 }
865
866 private double updateAndGetDisplayPosition(TickMark<T> m) {
867 double displayPosition = getDisplayPosition(m.getValue());
868 m.setPosition(displayPosition);
869 return displayPosition;
870 }
871
872 /**
873 * Positions a text node to one side of the given point, it X height is vertically centered on point if LEFT or
874 * RIGHT and its centered horizontally if TOP ot BOTTOM.
875 *
876 * @param node The text node to position
877 * @param posX The x position, to place text next to
878 * @param posY The y position, to place text next to
879 * @param angle The text rotation
880 * @param side The side to place text next to position x,y at
881 */
882 private void positionTextNode(Text node, double posX, double posY, double angle, Side side) {
883 node.setLayoutX(0);
884 node.setLayoutY(0);
885 node.setRotate(angle);
886 final Bounds bounds = node.getBoundsInParent();
887 if (Side.LEFT.equals(side)) {
888 node.setLayoutX(posX-bounds.getWidth()-bounds.getMinX());
889 node.setLayoutY(posY - (bounds.getHeight() / 2d) - bounds.getMinY());
890 } else if (Side.RIGHT.equals(side)) {
891 node.setLayoutX(posX-bounds.getMinX());
892 node.setLayoutY(posY-(bounds.getHeight()/2d)-bounds.getMinY());
893 } else if (Side.TOP.equals(side)) {
894 node.setLayoutX(posX-(bounds.getWidth()/2d)-bounds.getMinX());
895 node.setLayoutY(posY-bounds.getHeight()-bounds.getMinY());
896 } else {
897 node.setLayoutX(posX-(bounds.getWidth()/2d)-bounds.getMinX());
898 node.setLayoutY(posY-bounds.getMinY());
899 }
900 }
901
902 /**
903 * Updates visibility of the text node and adds the tick mark to the path
904 */
905 private void updateTickMark(TickMark<T> tick, int index, double length,
906 double startX, double startY, double endX, double endY)
907 {
908 // check if position is inside bounds
909 if (tick.getPosition() >= 0 && tick.getPosition() <= Math.ceil(length)) {
910 if (isTickLabelsVisible()) {
911 tick.textNode.setVisible(!labelsToSkip.get(index));
912 }
913 // add tick mark line
914 tickMarkPath.getElements().addAll(
915 new MoveTo(startX, startY),
916 new LineTo(endX, endY)
917 );
918 } else {
919 tick.textNode.setVisible(false);
920 }
921 }
922 /**
923 * Get the string label name for a tick mark with the given value
924 *
925 * @param value The value to format into a tick label string
926 * @return A formatted string for the given value
927 */
928 protected abstract String getTickMarkLabel(T value);
929
930 /**
931 * Measure the size of the label for given tick mark value. This uses the font that is set for the tick marks
932 *
933 *
934 * @param labelText tick mark label text
935 * @param rotation The text rotation
936 * @return size of tick mark label for given value
937 */
938 protected final Dimension2D measureTickMarkLabelSize(String labelText, double rotation) {
939 measure.setRotate(rotation);
940 measure.setText(labelText);
941 Bounds bounds = measure.getBoundsInParent();
|